Pattern 04 — animation-timeline, zero JavaScript
Scroll timelines in pure CSS
Every other page in this repo pays a JavaScript tax: listeners, observers,
requestAnimationFrame bookkeeping. This page pays nothing.
animation-timeline re-times ordinary CSS keyframe animations against
scroll position instead of the clock — scroll() binds to a scroll
container's progress, view() to an element's own journey across the
viewport. The browser runs both off the main thread, which means these effects
keep their frame rate even while your app is busy doing worse things.
There is no script tag in this file. View source and check.
@supports, keep the base state complete.
Demo 01 is already running
The blue bar pinned to the top of this page is a scroll-driven animation: a
one-keyframe scaleX re-timed against the document scroller. The
entire implementation:
.reading-bar span {
transform-origin: left center;
animation: grow-x linear both; /* linear: meters must not ease */
animation-timeline: scroll(root); /* 0% = page top, 100% = bottom */
}
@keyframes grow-x {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
Compare that with the JavaScript version on the progress page — ten lines there, zero here, identical pixels. The JS version's advantage is reach: it works in every browser that runs script. Pick per audience; ship the @supports fallback either way.
Demo 02 — view(): scale on entry
This figure starts at 86% scale and low opacity, reaching full size by the time it
has crossed 45% of the viewport. The timeline is the element's own visibility —
no coordinates, no math, one animation-range declaration:
Demo 03 — view(): slide on entry
Browser support
| Browser | Status |
|---|---|
| Chrome / Edge 115+ | Supported |
| Opera 101+ | Supported |
| Safari 26+ | Supported (earlier Safari: no) |
| Firefox | In development — behind the layout.css.scroll-driven-animations.enabled flag |
The deployment rule that makes partial support a non-issue: author the page so it
is complete without the animation, then add motion inside
@supports (animation-timeline: scroll()). Never put an element's
visible state behind a feature the browser might not have — the from-state belongs
in the keyframes, not in the base rule. Browsers that understand the timeline get
the choreography; everyone else gets a finished, static page and no way to tell
anything is missing.