On this page
Scroll-driven animation links visual progress to a scroll position rather than elapsed time. A reading bar can fill as a document moves, while an illustration can reveal as it enters the viewport. Neither effect requires intercepting wheel events or changing the user's scrolling physics.
Separate the timeline from the artwork
Start with an effect that remains useful when it does not animate. For a reading bar, choose a solid fill or create a restrained treatment with the gradient generator. Export the gradient as a static background. The timeline should control the bar's scale, not continually rebuild its colors.
Decide what progress means before writing CSS. Document progress and an element's passage through a viewport are different measurements. A short article, a nested scroll container, and a long page with a large footer can each produce surprising results if the timeline source is chosen casually.
Treat support as progressive enhancement. Browser implementations and individual subfeatures can differ, so verify the exact syntax against current support before shipping. A visible document must never depend on a scroll-linked opacity animation being supported.
Choose scroll() for container progress
An anonymous scroll() timeline follows a scroll container. The root scroller is a useful source for a page-level reading indicator. Using a scale transform avoids changing the bar's layout width every time progress changes.
.reading-progress {
position: fixed;
inset: 0 0 auto;
height: 3px;
transform-origin: left;
background: #315beb;
display: none;
}
@supports (animation-timeline: scroll()) {
.reading-progress {
display: block;
animation: fill linear both;
animation-timeline: scroll(root block);
}
}
@keyframes fill {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
.reading-progress { display: none; }
}The hidden fallback avoids presenting a permanently full bar as if it were live progress. This sample treats the indicator as decorative; the article itself still supplies headings and location cues. Do not announce every small scroll change through a live region.
Declare animation-timeline after the animation shorthand, which can reset timeline-related values. That ordering mistake is a frequent reason an effect unexpectedly returns to a normal time-based animation. The MDN scroll-driven animation guide explains the timeline model in more detail.
Choose view() for element passage
A view timeline measures an element's visibility progression relative to its scrollport. It fits local reveals, image treatments, and demonstrations tied to a particular section. The range needs deliberate design: animating across the element's whole passage is different from completing the reveal shortly after entry.
.figure { opacity: 1; transform: none; }
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.figure {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 35%;
}
}
}
@keyframes reveal {
from { opacity: .35; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}The default style remains fully visible. Enhancement is applied only when both support and motion preference permit it. For important text, avoid even the partial-opacity starting point; keeping copy readable throughout is more important than making an entrance obvious.
Test the range on unusually tall and short elements. A viewport-sized illustration and a small caption do not pass through visibility milestones in the same way. Review MDN's animation-range reference when named ranges produce results that differ from intuition.
Preserve native scrolling and meaningful fallback
A scroll timeline should observe navigation, not take ownership of it. Avoid trapping the page in a long pinned section just because a prototype needs more animation time. Users may navigate through keyboard scrolling, a scrollbar thumb, anchor links, or assistive technology rather than a smooth stream of wheel events.
Make reversed movement coherent. Unlike an entrance triggered once, a scroll-linked reveal can run backward whenever someone scrolls up. If disappearing content would be confusing, use a different interaction model instead of trying to force every effect onto a scroll timeline.
Do not add a second smoothing layer without a clear need. Interpolating a scroll-linked progress value through a delayed JavaScript animation can make the indicator lag behind actual position. It can also complicate anchor navigation and troubleshooting.
Use stable layout dimensions around animated media. The responsive container guide helps establish predictable containers, while an aspect ratio calculator can help reserve media space. These layout decisions keep the timeline's geometry from changing unexpectedly as images load.
If support is missing, preserve the final visual state or omit the decoration. A polyfill is not automatically necessary. Its runtime cost and behavioral differences should be weighed against the value of a nonessential effect.
Profile real pages and edge cases
A CSS timeline does not make every animated property cheap. Transform and opacity are sensible first choices, but a large transformed layer can still consume memory. Animating filter blur, large shadows, or repeated background paints may create visible frame drops on a modest device.
Record a full scroll interaction with real images and text. Compare the effect enabled and disabled. Look for layout instability, paint activity, and delayed input, not merely a reassuring frame-rate number in an empty demonstration. The web.dev animation performance guide provides a practical starting point for this analysis.
Test deep links that begin halfway down the page, restoration after browser back navigation, viewport resizing, and a page too short to scroll. Also test a nested container: the nearest scroller may not be the document you intended to observe.
Finally, test reduced motion on the actual operating system as well as browser emulation. A page with several individually subtle scroll effects can create substantial cumulative movement. Keep only effects that explain structure, preserve orientation, or provide genuinely useful progress feedback.
Verify the source with a geometry experiment
Build a test page with a short article followed by a long footer. Scroll until the article ends and inspect the reading bar. A root timeline measures the whole scrollable document, so the bar may still show substantial progress remaining. That is correct for document progress but misleading if its label promises article progress. Either describe the indicator accurately or choose a timeline and range tied to the article's passage.
Next, place the same demonstration inside a fixed-height panel with overflow scrolling. Move the panel scrollbar while leaving the document stationary, then reverse the experiment. This quickly reveals whether the effect follows the intended container. Record the source, axis, subject, and completion point with the component example so later layout changes do not silently change its meaning.
Reserve image space before running the experiment. When an image expands after loading, the available scroll distance changes and the same physical position can represent a different percentage. Test with a cold cache and delayed media as well as a fully loaded page. A stable result is more important than a perfectly smooth recording made only under ideal conditions.
Key takeaways
- Use scroll timelines for container progress and view timelines for element passage.
- Keep content readable before enhancement and during reverse scrolling.
- Verify animation range geometry across viewport and content sizes.
- Preserve native scrolling and profile the properties you animate.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor