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:
- The start of the scroll range maps to
0%progress of the@keyframes. - The end of the scroll range maps to
100%progress of the@keyframes. - Scrolling back and forth moves the animation timeline forward and backward dynamically.
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:
- Scroller source:
root(the document viewport),nearest(the closest scroll ancestor, which is the default), orself. - Scroll axis:
block(default vertical scrolling in standard text layouts),inline(horizontal reading direction),y, orx.
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:
cover: From the moment the element first starts entering the scrollport until it has completely exited.contain: From the moment the element is fully inside the scrollport until it first begins to exit.entry: From when the element first starts entering until it is fully inside.exit: From when the element starts exiting until it has completely left.entry-crossing/exit-crossing: Defined for scenarios where the element's size exceeds the viewport.
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.