This demo has several versions:
scroll-timeline
ScrollTimeline
In this demo the cards at the top stack onto each other. When a card is stuck, it scales down so that the following card stacks on top of it.
Original demo by CodeHouse: https://codyhouse.co/tutorials/how-stacking-cards
const $cardsWrapper = document.querySelector('#cards'); const $cards = document.querySelectorAll('.card__content'); // Pass the number of cards to the CSS because it needs it to add some extra padding. // Without this extra padding, the last card won’t move with the group but slide over it. const numCards = $cards.length; $cardsWrapper.style.setProperty('--numcards', numCards); // Each card should only shrink when it’s at the top. // We can’t use exit on the els for this (as they are sticky) // but can track $cardsWrapper instead. const viewTimeline = new ViewTimeline({ subject: $cardsWrapper, axis: 'block', }); $cards.forEach(($card, index0) => { const index = index0 + 1; const reverseIndex = numCards - index0; const reverseIndex0 = numCards - index; // Scroll-Linked Animation $card.animate( { // Earlier cards shrink more than later cards transform: [ `scale(1)`, `scale(${1 - (0.1 * reverseIndex0)}`], }, { timeline: viewTimeline, fill: 'forwards', rangeStart: `exit-crossing ${CSS.percent(index0 / numCards * 100)}`, rangeEnd: `exit-crossing ${CSS.percent(index / numCards * 100)}`, } ); });
To keep the cards stuck, position: sticky is used. Key for this part, is that this stickyness is not applied on the cards themselves (.card) but on the inner .card__content.
position: sticky
.card
.card__content
As for the animation, the key part is that the wrapping element #cards is being tracked on the entry-crossing range.
#cards
entry-crossing
The content of each card (.card__content) are animated and are assigned to a part of that range. As this demo contains 4 cards, each card should animate over 25% of the range. This is calculated using the number of cards and the index of each card.
4
25%
By animating the .card__content elements and not the .card elements, the height of the #cards wrapper – and thus the available scroll estate – is not affected.
⚠️ Your browser does not support Scroll-driven Animations. Please use Chrome 115 or newer.
👇 Scroll down to see the effect.
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Read more