How to Use CSS scroll-timeline for Scroll Animations

This article explores the CSS scroll-timeline property, explaining how it enables modern web developers to create scroll-driven animations natively in CSS without relying on external JavaScript libraries. You will learn the mechanics behind the property, its constituent syntax, and how to bind standard keyframe animations directly to the scroll offsets of a scrollable container to build performant, hardware-accelerated user experiences.


What Is the scroll-timeline Property?

The scroll-timeline property is part of the CSS Scroll-driven Animations Module. Traditionally, CSS animations are tied strictly to time using the animation-duration property. With scroll-timeline, an animation's progression is decoupled from monotonic clock time and linked directly to the scroll position of a scroll container (the "scrollport").

As the user scrolls from the beginning of the container to the end, the animation advances from 0% to 100%. If scrolling stops, the animation pauses at that exact progress point; if the user scrolls backward, the animation plays in reverse.

Syntax and Sub-Properties

The scroll-timeline property is a shorthand for two individual properties:

  1. scroll-timeline-name: Defines a custom identifier (prefixed with --) used to reference the timeline on animated elements.
  2. scroll-timeline-axis: Specifies the scroll direction that drives the timeline. Accepted values are block (default), inline, y, or x.
/* Shorthand syntax */
.scroll-container {
  scroll-timeline: --pageTimeline block;
}

/* Longhand equivalent */
.scroll-container {
  scroll-timeline-name: --pageTimeline;
  scroll-timeline-axis: block;
}

How scroll-timeline Binds to Container Scroll Offsets

To link an element's animation to a container's scroll progress, the browser establishes a link between the scroller, the defined timeline, and the animated element using the following process:

1. Define the Scroll Container

You must establish a scroll context by setting an overflow property (such as overflow: auto or overflow-y: scroll) on the parent or root element and assigning it a named scroll-timeline.

.scroller {
  overflow-y: auto;
  scroll-timeline: --readingProgress y;
}

2. Define Keyframes Independent of Time

Keyframes represent the visual transformation from the scroll start offset (0%) to the scroll end offset (100%).

@keyframes stretch {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

3. Attach the Timeline to the Animated Element

On the element you wish to animate, declare the @keyframes name using animation-name and map it to the defined timeline using animation-timeline. The animation-duration property is ignored or omitted.

.progress-bar {
  animation-name: stretch;
  animation-timeline: --readingProgress;
  transform-origin: left;
}

Mapping Scroll Offsets to Keyframe Percentages

By default, the scroll timeline starts at the minimum scroll offset (0px) and ends at the maximum scroll offset (scrollHeight - clientHeight):

  • Scroll Offset: 0px \(\rightarrow\) Animation frame: 0%
  • Scroll Offset: 50% of max scroll \(\rightarrow\) Animation frame: 50%
  • Scroll Offset: Maximum reachable scroll \(\rightarrow\) Animation frame: 100%

For granular control, CSS also provides the animation-range property, which lets you define custom start and end scroll offsets rather than spanning the entire container scroll track.

The Anonymous scroll() Alternative

If you do not need to share a timeline between unrelated elements, CSS allows you to define an inline, anonymous scroll timeline directly on the animated element using the scroll() functional notation:

.progress-bar {
  animation: stretch auto linear;
  animation-timeline: scroll(nearest block);
}

The scroll() function automatically searches the DOM tree for the nearest ancestor with a scrollable overflow, binding the animation to its scroll offsets without requiring explicit naming on the parent.

Advantages of Native Scroll Timelines

  • Compositor-Thread Execution: Unlike JavaScript scroll event listeners or requestAnimationFrame loops, CSS scroll-driven animations run off the main thread on the browser compositor, preventing frame drops and layout thrashing.
  • Declarative Code: Replaces complex position calculation scripts with concise, maintainable CSS declarations.
  • Responsive by Default: When window dimensions or layout sizes shift, the browser automatically recalculates the container scroll bounds without manual re-initialization.