scroll-driven Animations Intro
CSS scroll-driven animations — animation-timeline with scroll() and view(), progress-linked effects, support caveats, and reduced motion.
- css
- scroll-driven
- animations
Scroll-driven animations link a CSS animation’s progress to scroll position instead of time. Parallax-style fades, progress bars, and reveal-on-enter effects can run on the compositor path without scroll event JavaScript — where supported.
Docs: MDN scroll-driven animations, animation-timeline, Scroll-driven Animations explainer.
Core idea
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress {
transform-origin: left;
animation: grow linear both;
animation-timeline: scroll(root block);
}
animation-timeline: scroll(...) uses a scroll timeline. Progress 0→1 maps to keyframes as the user scrolls that scroller.
scroll() vs view()
/* Scroll timeline: driven by a scroll container’s position */
.progress {
animation-timeline: scroll(nearest block);
}
/* View timeline: driven by an element’s progress through the viewport/scrollport */
.card {
animation: fade-up linear both;
animation-timeline: view(block);
animation-range: entry 0% cover 40%;
}
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(1rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
| Timeline | Driver |
|---|---|
scroll() |
Overall scroll progress of a named/nearest/root scroller |
view() |
Subject element’s visibility progress in a scrollport |
animation-range clips which part of the timeline maps to the animation (entry, exit, cover, contain percentages).
Named timelines
.scroller {
overflow: auto;
scroll-timeline-name: --main;
scroll-timeline-axis: block;
}
.scroller .bar {
animation: grow linear both;
animation-timeline: --main;
}
Useful when the animated element isn’t a direct child of the scroller you care about.
Progressive enhancement
Support is improving but not universal. Always provide a sensible static end state:
.card {
opacity: 1;
transform: none;
}
@supports (animation-timeline: view()) {
.card {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
}
Reduced motion
@media (prefers-reduced-motion: reduce) {
.card,
.progress {
animation: none !important;
}
}
Scroll-linked motion can be as nauseating as time-based motion. Prefer opacity-only or no animation under reduce.
When not to use
- Critical form UX that must not depend on scroll progress
- Complex choreography needing WAAPI cancelation APIs across browsers without support
- Effects that fight scrolljacking accessibility guidelines
Interview out-loud
“Scroll-driven animations use animation-timeline: scroll() or view() so keyframe progress tracks scrolling instead of time. view() is great for reveal-on-enter; scroll() for page progress bars. I gate with @supports, keep static styles as fallback, and disable under prefers-reduced-motion.”
Footguns
- Assuming Safari/Firefox parity without checking current baseline.
- Animating layout properties on scroll timelines — still costly.
- Ignoring reduced motion.
- Nested scrollers — wrong timeline subject.
- Combining
animation-durationexpectations — duration is less meaningful when the timeline is scroll-linked (progress is scroll-based).
Progress bar recipe
<div class="read-progress" aria-hidden="true"></div>
.read-progress {
position: fixed;
inset-inline: 0;
inset-block-start: 0;
height: 3px;
background: var(--brand);
transform-origin: 0 50%;
transform: scaleX(0);
animation: grow-x linear;
animation-timeline: scroll(root block);
z-index: 400;
}
@keyframes grow-x {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
.read-progress { animation: none; transform: none; display: none; }
}
Keep decorative; do not use as the only reading position indicator if users need precision — a percentage text readout can complement it for long docs tools.
Related
- CSS animations keyframes
- prefers-reduced-motion
- Overflow scroll and scroll snap
- transform and opacity performance
Further reading
Related guides
- CSS Animations @keyframesDefine @keyframes, control duration, easing, iteration, and fill-mode — when to animate, what to animate, and reduced-motion defaults.
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.