interactionadvanced

Scroll-Driven Animation

CSS animations tied to scroll position rather than time, using the 2023 Scroll-Driven Animations API with animation-timeline: scroll() or view().

Plain English

Traditional animations run on a clock — they start, play for 300ms, and end. Scroll-driven animations run on the scroll bar: as you scroll down, the animation advances; scroll back up, and it reverses. This means a progress bar that fills as you read, a hero image that fades in as it enters the viewport, or a sticky header that shrinks as you scroll past — all in pure CSS with zero JavaScript. The two timeline types serve different purposes: scroll() tracks how far the user has scrolled in a container (0% = top, 100% = bottom), while view() tracks when an element enters and exits the viewport. Because the animation is driven by scroll position rather than time, it naturally plays forward and backward as the user scrolls in either direction.

Technical

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .card { animation: fadeIn linear; animation-timeline: view(); animation-range: entry 0% entry 30%; }. For scroll progress: animation-timeline: scroll(root block) ties to page scroll. animation-range controls when in the scroll timeline the animation plays (e.g., entry = element entering viewport, exit = leaving). Browser support: Chrome 115+, Edge 115+, Firefox 110+ (with flag), Safari in progress as of 2025. For fallback: detect via @supports (animation-timeline: scroll()) and use IntersectionObserver for unsupported browsers. Performance: scroll-driven animations run off the main thread in supporting browsers — they are compositor-based and do not block JS.

Live Demo

Scroll-Driven Animation

Drag the slider to simulate scroll progress and see which elements would animate at each position.

Scroll position0%
Page loadHero fades in✓ triggered
25% scrollFeature cards slide up
50% scrollStats counter starts
75% scrollTestimonials appear
100% scrollCTA pulses into view

CSS snippet

@keyframes fadeSlideUp {
  from { opacity: 0; translate: 0 40px; }
  to   { opacity: 1; translate: 0 0; }
}
.scroll-reveal {
  animation: fadeSlideUp linear both;
  animation-timeline: scroll();
  animation-range: entry 0% entry 50%;
}

scroll-driven animations are tied to scroll position — no JS event listeners needed.

Usage

✓ Good usage

A reading progress bar at the top of a long article that fills from 0% to 100% as the user scrolls to the bottom, implemented in 4 lines of CSS with no JavaScript.

✗ Bad usage

Pinning an entire section and rotating a 3D globe as the user scrolls through it — complex 3D transforms tied to scroll are better handled by GSAP ScrollTrigger on WebGL canvases, not CSS scroll-driven animations.

Common mistakes

AI Prompt Example

Copy this into Claude, Cursor, Bolt, or v0.

Add scroll-driven reveal animations to the features section cards using the CSS Scroll-Driven Animations API. Each card should animate from opacity: 0 and translateY: 32px to fully visible as it enters the viewport. Use animation-timeline: view() with animation-range: entry 0% entry 50%. Wrap in @supports (animation-timeline: scroll()) and add an IntersectionObserver fallback that adds a .visible class for unsupported browsers. Respect prefers-reduced-motion by setting animation-duration: 0.01ms in the reduced-motion media query.