What scroll-driven animations are
Scroll-driven animations let CSS run an animation against a scroll rather than against the document’s wall clock. Two timeline kinds are defined:
scroll()— the animation progresses with the page (or named scroller) scroll position.view()— the animation progresses as the target element scrolls into and out of view.
.progress-bar {
position: fixed;
inline-size: 100%;
block-size: 4px;
background: var(--c-brand-primary);
transform-origin: left;
animation: progress linear;
animation-timeline: scroll();
}
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
That single declaration replaces about 30 lines of JavaScript
that listened to scroll, computed scroll fraction, and updated
a CSS variable.
Named timelines
The view-timeline property names a timeline driven by an
element’s intersection with its scrolling ancestor. Then any
animation can attach to it via animation-timeline:
.card {
view-timeline-name: --card;
animation: fade-in linear;
animation-timeline: --card;
animation-range: entry 0% cover 30%;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; }
}
Named timelines are scoped: a view-timeline-name defined on
.card is visible to descendants and to the next-sibling chain
unless timeline-scope is widened.
animation-range
The animation-range property says which portion of the
timeline drives the animation:
| Keyword | Meaning |
|---|---|
cover 0% to cover 100% | The element is entering the scrollport from any position. |
entry 0% to entry 100% | From the moment any pixel enters the scrollport to the moment the entry edge reaches the scrollport edge. |
exit 0% to exit 100% | The mirror of entry, on the way out. |
contain 0% to contain 100% | While the element is fully visible. |
A common pattern: animate from 0% opacity at entry 0% to 100%
opacity at entry 50%. The element fades in over the first half
of its entry into the scrollport.
Reduced motion
The platform respects prefers-reduced-motion only if the author
writes the rule. Default scroll-driven animations do run when
reduced motion is set, so wrap:
@media (prefers-reduced-motion: reduce) {
.card {
animation: none;
}
}
The 30%-of-macOS-Safari-users figure for reduced-motion preference (Apple’s 2023 telemetry) makes this an essential guard.
Cross-engine support
| Engine | Support |
|---|---|
| Chromium | 115 (July 2023) |
| Gecko (Firefox) | Behind layout.css.scroll-driven-animations.enabled flag as of 2025; ship target 2025–2026 |
| WebKit | In development, prototype 2024; production target unset |
The feature is currently a Chromium-led experiment; a runtime
fallback to IntersectionObserver is the right pattern for
authors who want broad coverage:
@supports (animation-timeline: scroll()) {
.reveal {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
}
Outside the @supports block, the elements remain visible (no
animation needed if not supported). The pattern is progressive
— users on Chromium see the polish; everyone else sees the
content.
Common patterns
Reading-progress indicator
.progress {
position: fixed; top: 0; left: 0; right: 0;
block-size: 3px;
background: var(--c-brand-primary);
transform-origin: left;
animation: progress linear;
animation-timeline: scroll(root);
}
@keyframes progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }
Hero parallax
.hero-image {
animation: parallax linear;
animation-timeline: view();
animation-range: entry 0% exit 100%;
}
@keyframes parallax {
to { transform: translateY(-30%); }
}
Sticky header that shrinks on scroll
header {
animation: shrink linear both;
animation-timeline: scroll(root);
animation-range: 0 200px;
}
@keyframes shrink {
from { padding-block: var(--space-5); }
to { padding-block: var(--space-2); }
}
Common pitfalls
- Animation runs on first paint. Scroll-driven animations
evaluate at scroll 0 — so an
entry 0%keyframe is the initial state. Set sensible defaults outside the keyframes. animation-fill-modeconfusion. Usebothto apply the initial keyframe before the timeline starts and the final keyframe after.- Heavy effects on
transform/opacityonly. These are composited; other properties (e.g.width) trigger layout per scroll frame. Stick to compositor-friendly properties. - Named
view-timelinecollisions. Two elements with the sameview-timeline-namecreate ambiguity; the first ancestor wins. - Forgetting reduced-motion. Animate respectfully.
Further reading
- W3C Scroll-driven Animations.
- Bramus’s scroll-driven-animations.style is the canonical author reference; about 40 worked examples.
- Adam Argyle’s
@scroll-timeline-to-animation-timelinemigration for code from the older syntax. - State of CSS 2024 reports about 25% of respondents had used scroll-driven animations in production by end of 2024.