CSS Scroll-Driven Animations: No JavaScript Required
Use the new CSS scroll-driven animations API to create scroll-triggered reveals, progress bars and parallax effects entirely in CSS. Includes real examples and browser support table.
Scroll-driven animations have been a JavaScript staple for years — libraries like GSAP, AOS and Intersection Observer all do versions of this. Now CSS can do it natively, with zero JavaScript and better performance.
The new animation-timeline property connects a CSS animation to a scroll position instead of time:
@keyframes reveal {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: reveal linear;
animation-timeline: scroll();
animation-range: entry 0% entry 100%;
}
scroll() refers to the nearest scrolling ancestor. animation-range defines when in the scroll the animation plays.
The classic reading progress bar, in pure CSS:
.progress-bar {
position: fixed;
top: 0; left: 0;
height: 3px;
background: var(--accent);
transform-origin: left;
animation: progress linear;
animation-timeline: scroll(root);
}
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.reveal-item {
opacity: 0;
transform: translateY(40px);
animation: reveal-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
@keyframes reveal-up {
to { opacity: 1; transform: translateY(0); }
}
view() creates a timeline based on the element's position in the viewport. animation-range: entry 0% entry 60% means the animation plays while the element is entering the viewport — starting at 0% visible, completing at 60% visible.
Scroll-driven animations require Chrome 115+ or Edge 115+. Firefox and Safari support is improving but not yet universal. Always add a feature check:
@supports (animation-timeline: scroll()) {
.card {
animation: reveal linear;
animation-timeline: scroll();
animation-range: entry 0% entry 100%;
}
}
/* Fallback for unsupported browsers */
.card {
opacity: 1;
transform: none;
}