Under The Hood
frontend css Scroll-driven Animations (Chrome 115+)

Scroll-Driven Animations: animation-timeline: scroll() Compositor Sync

Last updated
Prerequisites:
CSS animations and keyframes
Understanding of scroll events and the compositor
  • css
  • scroll-driven-animations
  • animation-timeline
  • compositor
  • scroll
  • keyframes

Read at your depth

The practical view

animation-timeline: scroll() ties an animation's progress to scroll position instead of time. .progress { animation: grow linear; animation-timeline: scroll(); } makes the animation play as you scroll (0% at scroll start, 100% at scroll end). Use scroll(root) for the page or scroll(#container) for an element; the range option (e.g. scroll(nearest) or animation-range: cover 0% cover 100%) sets which scroll portion maps to the animation. This enables scroll-linked effects (progress bars, parallax, reveal-on-scroll) in pure CSS with no scroll listeners.

The same idea in other frameworks

react equivalent
React components can drop scroll-position JS state in favor of CSS animation-timeline for progress/parallax effects.

Legacy vs modern

Scroll listener + rAF progress vs compositor scroll() timeline

JS scroll handling samples on the main thread and can jank; animation-timeline: scroll() drives progress on the compositor with zero main-thread work.

before → after
Scroll listener + style update
addEventListener('scroll', () => {
  bar.style.transform = `scaleX(${scrollY / max})`; // ❌ OLD: main-thread per scroll event
});
Compositor-driven scroll() timeline
.progress-bar { animation: grow linear; animation-timeline: scroll(); } /* ✅ NEW */

Interview gotchas

Context

This separates candidates who understand the compositor from those who only know 'scroll effects' — the main-thread vs compositor-thread distinction is the substance.

The mechanical answer

A scroll listener runs on the main thread: the scroll event fires, JS runs (possibly scheduling a rAF), styles change, and the engine recalculates style and paint — all main-thread work that competes with input handling and rendering. Scroll-driven animations define a ScrollTimeline whose currentTime is the scroll offset; for compositor-eligible properties (transform, opacity), the compositor thread updates the animation progress directly from its own scroll position, bypassing the main thread entirely. The result is frame-aligned progress updates that cannot be starved by main-thread work. Even for non-compositable properties, the engine still benefits from a single consistent timeline source rather than event-driven JS sampling.

Trap

The naive answer is 'it's just a CSS shortcut for the same scroll listener'. It is a different execution model (compositor-driven vs main-thread event-driven). Another trap: assuming any property animates smoothly — only transform/opacity run fully on the compositor; animating width/height/top still engages main-thread layout each frame. Candidates who mention compositor eligibility and the ScrollTimeline source demonstrate real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.