Scroll-Driven Progress Indicator
CSS Scroll-Driven Animations
Scroll-Driven Progress Indicator
Zero JavaScript.
The Technique
The scroll-driven animations API lets you tie an animation's progress directly to scroll position, replacing the most common reason to reach for JavaScript on a reading progress bar.
@supports (animation-timeline: scroll()) {
.progress {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
block-size: 3px;
transform-origin: 0 50%;
transform: scaleX(0);
animation: progress linear both;
animation-timeline: scroll(root);
}
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
}
How It Works
animation-timeline: scroll(root) links the animation's progress to the root element's scroll
position. As you scroll from top to bottom, the animation plays from from to to. The
bar starts at scaleX(0) and ends at scaleX(1), producing the fill effect.
transform-origin: 0 50% is the key detail: it anchors the scale transform to the left edge so
the bar grows right rather than expanding from the centre.
The @supports Wrapper
Scroll-driven animations are a progressive enhancement. Wrapping the whole thing in
@supports (animation-timeline: scroll()) means browsers without support simply don't show the
bar; a clean absence rather than a broken presence. No polyfill, no fallback JavaScript needed.
Animation Range
The animation-range property gives fine control over when within the scroll range the animation
plays. entry 0% exit 0% means: start when the element enters the scroll container, finish when it
exits. For a document-level bar, this maps to the full page scroll.
You can use this for scroll-triggered reveals too: set a range that covers just one section and the animation only plays when that section is in view.
Browser Support
Scroll-driven animations landed in Chrome 115 and Edge 115. Firefox shipped support in version 110 behind a
flag, with full support in 132. Safari support arrived in 18.2. The @supports wrapper handles the
gap cleanly in older browsers.
What This Replaces
The JavaScript equivalent requires a scroll event listener, a handler that reads
scrollY, calculates a percentage against document.body.scrollHeight, and updates a
style property, typically with a requestAnimationFrame wrapper for performance. That is eight to
fifteen lines of JavaScript that this one CSS property replaces entirely.
Scroll to the bottom to see the bar complete.