scroll() Timeline
Binds an animation to the scroll progress of the nearest scroll container (or root). The animation plays from 0% at the top to 100% at the bottom -- no keyframe duration needed, the scroll position IS the timeline.
Parallax, progress indicators, and reveal effects powered entirely by CSS --
animation-timeline: scroll() and view().
No IntersectionObserver. No scroll event listeners. Zero JavaScript.
Uses scroll() for page-level progress, view() for
per-element reveal, and animation-range to constrain timing.
Stable Chrome 115+ · Edge 115+ · Firefox behind flag
Everything on this page is driven by your scroll position
view() timeline
Each card uses animation-timeline: view() to animate as it enters the viewport.
The animation-range: entry constraint means it only animates during the entry phase.
Binds an animation to the scroll progress of the nearest scroll container (or root). The animation plays from 0% at the top to 100% at the bottom -- no keyframe duration needed, the scroll position IS the timeline.
Tracks an element's visibility within its scroll container. The animation plays based on how much of the element is visible, from entering (0%) to fully visible to exiting (100%). Perfect for reveal-on-scroll effects.
Constrains which part of the timeline drives the animation.
entry 0% entry 100% means "only animate during the entry phase."
You can also use exit, contain, or cover.
Unlike IntersectionObserver-based solutions, scroll-driven animations are declarative CSS. They run on the compositor thread, are automatically hardware-accelerated, and don't cause layout thrashing. Scroll handlers are the old way.
Traditional parallax requires listening to scroll events and manually computing offsets. With scroll(), you just declare a transform keyframe and bind it to the scroll timeline. The browser handles everything, including subpixel smoothing.
These bars fill as you scroll through the page, each bound to the root scroll() timeline.
/* 1. Page-level scroll progress */ .progress-bar { animation: grow linear; animation-timeline: scroll(); } /* 2. Per-element reveal on viewport entry */ .card { animation: fade-in linear both; animation-timeline: view(); animation-range: entry 0% entry 100%; } /* 3. Parallax (slower scroll on background) */ .bg { animation: shift linear; animation-timeline: scroll(); } @keyframes shift { from { transform: translateY(-5%); } to { transform: translateY(5%); } }