Cards that stack as you scroll
Live demo
scroll inside the frame
Caveat and fallback
CaveatThe obvious version animates each card on its own view() timeline, and it never moves: a stuck card never leaves the scrollport, so its exit range never starts and the card stays at full size while the stack forms over it. The card that recedes has to be driven by the card arriving on top of it, which means naming the next card's view-timeline and pointing the earlier card at it. Names are only visible to the element that declares them and its descendants, so the scroller has to hoist them with timeline-scope. Leave that out and the name does not resolve, and every card sits in its end state, small and dark, before anyone has scrolled.
FallbackAn engine without scroll timelines does not skip the animation; it runs it as an ordinary one with no duration and lands every card on its last keyframe. So the whole recession goes inside @supports (animation-timeline: view()). Outside it the cards still stick at stepped offsets and pile up as you scroll, with the depth effect missing and nothing broken.
Source code
.stack {
overflow-y: auto;
/* or the names stay private */
timeline-scope: --c2, --c3;
}
.card {
position: sticky;
top: 10px;
transform-origin: top;
}
.card:nth-child(2) {
top: 20px;
view-timeline: --c2 block;
}
.card:nth-child(3) {
top: 30px;
view-timeline: --c3 block;
}
@supports (animation-timeline: view()) {
.card {
animation: recede linear both;
animation-range: entry 0% entry 100%;
}
/* the next card drives this one */
.card:nth-child(1) { animation-timeline: --c2; }
.card:nth-child(2) { animation-timeline: --c3; }
.card:last-child { animation: none; }
}
@keyframes recede {
to { scale: .9; filter: brightness(.55); }
}
Published