Seven demos that explain CSS scroll-driven animations: scroll() and view() side by side, cards that fade in on entry and out on exit, a named timeline shared across siblings, a horizontal timeline, words that light up as you read, an animation that runs while a section is pinned and view-timeline-inset.
The two timelines in one panel: the bar at the top uses scroll(), so it tracks how far the whole container has scrolled, while each card uses view(), so it tracks its own trip through the visible area. Seeing both at once makes the difference obvious.
View code
<div class="sda1" tabindex="0" role="region" aria-label="Scroll this panel"><div class="bar" aria-hidden="true"></div><p>scroll(): the bar. view(): each card.</p><div class="c">Card A</div><div class="c">Card B</div><div class="c">Card C</div></div>
One keyframe set with four stops: hidden, visible, visible, hidden. With animation-range: entry 0% exit 100%, the first quarter plays as the card enters and the last quarter as it leaves, so cards fade in at the bottom and out at the top.
The animated element does not have to be inside the scroller. The list names its timeline with scroll-timeline: --sda3 y, a shared parent widens its reach with timeline-scope, and a counter badge outside the list rotates as the list scrolls.
A horizontal gallery whose progress bar fills as you scroll sideways. scroll(x) is the only change from a vertical timeline. The gallery is a focusable region so keyboard users can scroll it with the arrow keys.
Each word of a statement brightens from dim to full as it scrolls through the middle of the panel, a popular storytelling effect. Every word is a span with its own view() timeline and a tight animation-range, so no script is involved.
A sticky box stays in place while its tall parent scrolls past, and during that stretch a shape inside it turns and changes colour. The animation's timeline is the tall parent's view() and its range is contain, the part where the parent fills the viewport.
View code
<div class="sda6" tabindex="0" role="region" aria-label="Scroll this panel"><p>Scroll ↓</p><section><div class="stick"><span class="shape" aria-hidden="true"></span><b>Pinned while it turns</b></div></section><p>After the pinned section.</p></div>
view-timeline-inset moves the edges the timeline measures against. Here an inset of 40 percent top and bottom means each bar only animates while it crosses a band in the middle of the panel, shown by two guide lines.
scroll() follows the scroll position of a container from top to bottom. view() follows one element's journey through the visible area, from entering to leaving. Most reveal effects want view(); progress bars want scroll().
Ranges choose the moment
animation-range picks part of the timeline: entry while the element enters, exit while it leaves, contain while it is fully visible and cover for the whole trip. Percentages refine each one.
Named timelines and scope
Name a timeline with scroll-timeline or view-timeline to use it on another element. If that element is not a descendant, declare the name with timeline-scope on a shared ancestor.
Always progressive
Wrap the rules in @supports (animation-timeline: view()) so browsers without support show the finished state. The animation should be a bonus, never the only way to see content.
Frequently asked questions
Do these work without JavaScript?
Yes. Every demo on this page is pure CSS.
Which browsers support animation-timeline?
Chromium browsers and Safari 26 at the time of writing; Firefox is working on it.
Why set animation-duration?
Scroll timelines ignore time, but some engines need a non-zero duration in the shorthand, so linear both with the default works best.