How Do CSS Scroll-Driven Animations Work?

CSS Scroll-Driven Animations connect the progression of keyframe animations directly to scroll progress rather than elapsed clock time. By introducing native scroll and view timeline controls via CSS properties like animation-timeline and animation-range, browsers can calculate and render scroll-linked effects directly on the compositor thread without requiring JavaScript event listeners.

From Time-Based to Scroll-Based Timelines

Traditional CSS animations rely on duration and time elapsed, specified using animation-duration in seconds or milliseconds. As time ticks forward, the animation interpolates between the keyframes defined in @keyframes.

Scroll-driven animations decouple animations from wall-clock time and bind them to a scroll container's scroll position. When a scroll-based timeline is attached to an element:

Types of Scroll Timelines

The CSS Scroll-Driven Animations specification defines two distinct types of timelines: Scroll Progress Timelines and View Progress Timelines.

1. Scroll Progress Timelines

A Scroll Progress Timeline links an animation to the overall scroll progress of a scroll container (the scrollport) from 0% (fully scrolled to the top/left) to 100% (fully scrolled to the bottom/right).

This can be declared anonymously using the scroll() function:

@keyframes progress-bar {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

.reading-progress {
  transform-origin: 0% 50%;
  animation: progress-bar linear;
  animation-timeline: scroll(root block);
}

The scroll() function accepts two optional arguments:

For named scrollers across separate DOM nodes, you can define scroll-timeline-name and scroll-timeline-axis on the container, then reference that name on the animated child using animation-timeline: --timeline-name.

2. View Progress Timelines

A View Progress Timeline links an animation to the visibility and position of a specific target element as it enters, traverses, and exits a scroll container.

This timeline is declared using the view() function:

@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(40px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.card {
  animation: fade-in-up linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

Controlling Ranges with animation-range

When using View Progress Timelines, an element travels through distinct visibility milestones relative to the scrollport. The animation-range property allows developers to scope the animation to precise intervals of this journey:

Range values can be paired with percentage offsets (such as animation-range: entry 20% cover 80%) to create customized trigger windows.

Performance and Architecture Benefits

Prior to native scroll timelines, synchronizing visual effects with scrolling required JavaScript via scroll event listeners, requestAnimationFrame, or the IntersectionObserver API. These patterns frequently cause layout thrashing and frame drops because JavaScript runs on the browser's main thread.

CSS Scroll-Driven Animations allow modern browser rendering engines to offload animation evaluation directly to the compositor thread. Because the browser knows the entire layout and keyframe sequence declaratively, scroll animations remain smooth and run at high refresh rates without locking the main thread.