|$ curl https://forge-ai.dev/api/markdown?path=docs/css/scroll-animations
$cat docs/scroll-driven-animations.md
updated Last week·12 min read·published

Scroll-Driven Animations

CSSScroll AnimationsScroll-DrivenAdvanced🎯Free Tools
What Are Scroll-Driven Animations?

Scroll-Driven Animations tie CSS animation progress to scroll position instead of time. This means an animation plays forward as you scroll down, backward as you scroll up, or based on when an element enters the viewport — all without JavaScript event listeners or IntersectionObserver.

The feature introduces two new timeline types: scroll() for progress tied to a scroll container, and view()for progress tied to an element's visibility in a scrollport. Both work with the standard CSS animation properties you already know.

scroll-progress.css
CSS
1/* Scroll progress indicator */
2.scroll-progress {
3 position: fixed;
4 top: 0;
5 left: 0;
6 height: 3px;
7 background: #3B82F6;
8 transform-origin: left;
9 animation: grow-width linear;
10 animation-timeline: scroll(root);
11}
12
13@keyframes grow-width {
14 from { transform: scaleX(0); }
15 to { transform: scaleX(1); }
16}
scroll() Timeline

The scroll() function creates a timeline based on the scroll position of a scroll container. You can reference the root document scroll, a specific element, or a particular axis.

scroll-timeline.css
CSS
1/* Root scroll — entire page */
2animation-timeline: scroll();
3
4/* Named scroll container */
5.scroller {
6 overflow-y: auto;
7 animation-timeline: scroll(nearest);
8}
9
10/* Axis specification */
11animation-timeline: scroll(root block); /* vertical scroll */
12animation-timeline: scroll(root inline); /* horizontal scroll */
13
14/* Full syntax */
15animation-timeline: scroll(<scroller> <axis>);
16
17/* Examples */
18.progress-bar {
19 animation-timeline: scroll(root block);
20}
21
22.horizontal-progress {
23 animation-timeline: scroll(.carousel inline);
24}
25
26/* Scroll snap container */
27.snap-container {
28 overflow-x: scroll;
29 scroll-snap-type: x mandatory;
30 animation-timeline: scroll(nearest inline);
31}

The scroll() function accepts three parameters: the scroller element (root, nearest, or self), and the axis (block, inline, or x/y). Default is the nearest scroll ancestor on the block axis.

📝

note

The timeline progresses from 0% (scroll position at start) to 100% (scroll position at end). These map directly to your animation's 0% and 100% keyframes.
view() Timeline

The view()function creates a timeline based on an element's visibility within its scrollport. As the element scrolls into view, the animation progresses. As it scrolls out, the animation reverses. This is the basis for scroll-triggered reveal animations.

view-timeline.css
CSS
1/* Reveal animation — element fades in as it enters view */
2.reveal {
3 opacity: 0;
4 transform: translateY(40px);
5 animation: fade-up linear both;
6 animation-timeline: view();
7 animation-range: entry 0% entry 100%;
8}
9
10@keyframes fade-up {
11 from {
12 opacity: 0;
13 transform: translateY(40px);
14 }
15 to {
16 opacity: 1;
17 transform: translateY(0);
18 }
19}
20
21/* Full syntax */
22animation-timeline: view(<axis> <range>);
23
24/* Specify the element to track (default: self) */
25animation-timeline: view(block);
26
27/* Track a different element */
28animation-timeline: view(of .scroll-container block);

The animation-range property controls which part of the view progress triggers the animation. Common values include entry, exit, contain, and cover.

animation-range.css
CSS
1/* Animation range keywords */
2.reveal {
3 animation-timeline: view();
4
5 /* Animate during the entry phase (0% to 100% of entry) */
6 animation-range: entry 0% entry 100%;
7
8 /* Animate during the entire visibility cycle */
9 animation-range: cover 0% cover 100%;
10
11 /* Animate while element is contained in viewport */
12 animation-range: contain 0% contain 100%;
13
14 /* Animate during exit */
15 animation-range: exit 0% exit 100%;
16
17 /* Combine entry and exit with custom percentages */
18 animation-range: entry 0% exit 50%;
19}
animation-timeline Property

The animation-timeline property replaces the time-based default with a scroll-based timeline. It accepts scroll(), view(), or a named timeline created with timeline-name.

animation-timeline.css
CSS
1/* Named timeline — reusable across elements */
2.scroller {
3 overflow-y: auto;
4 scroll-timeline-name: --page-scroll;
5 scroll-timeline-axis: block;
6}
7
8.progress-bar {
9 animation: grow linear;
10 animation-timeline: --page-scroll;
11}
12
13/* Element-based named timeline */
14.card {
15 view-timeline-name: --card-view;
16 view-timeline-axis: block;
17}
18
19.card-content {
20 animation: fade-in linear both;
21 animation-timeline: --card-view;
22 animation-range: entry 20% entry 80%;
23}
24
25/* Multiple animations with different timelines */
26.element {
27 animation:
28 color-shift linear scroll(root block),
29 fade-in linear view();
30 animation-timeline:
31 scroll(root block),
32 view();
33}
Parallax Effects

Parallax effects — where background elements move at a different speed than foreground content — are a natural fit for scroll-driven animations. You can achieve this with a single CSS rule and zero JavaScript.

parallax.css
CSS
1/* Background parallax — moves slower than scroll */
2.parallax-bg {
3 position: fixed;
4 inset: 0;
5 z-index: -1;
6 animation: parallax-shift linear;
7 animation-timeline: scroll(root block);
8}
9
10@keyframes parallax-shift {
11 from { transform: translateY(0); }
12 to { transform: translateY(30%); }
13}
14
15/* Hero section parallax — fades out on scroll */
16.hero {
17 animation: hero-fade linear both;
18 animation-timeline: scroll(root block);
19 animation-range: 0% 30%;
20}
21
22@keyframes hero-fade {
23 from {
24 opacity: 1;
25 transform: translateY(0);
26 }
27 to {
28 opacity: 0;
29 transform: translateY(-50px);
30 }
31}
32
33/* Multi-speed parallax layers */
34.parallax-layer-1 {
35 animation: layer1 linear;
36 animation-timeline: scroll(root block);
37}
38
39.parallax-layer-2 {
40 animation: layer2 linear;
41 animation-timeline: scroll(root block);
42}
43
44@keyframes layer1 {
45 from { transform: translateY(0); }
46 to { transform: translateY(20%); }
47}
48
49@keyframes layer2 {
50 from { transform: translateY(0); }
51 to { transform: translateY(40%); }
52}

info

Parallax with scroll-driven animations is GPU-accelerated because it uses transform — the same property the browser optimizes for compositing. This is far more performant than animating top or margin-top.
Reveal Animations

Reveal animations make elements fade, slide, or animate in as they scroll into view. Using view() timelines, you can create these effects for every element on the page without any JavaScript scroll detection.

reveal-animations.css
CSS
1/* Base reveal class — apply to any element */
2.reveal-on-scroll {
3 opacity: 0;
4 transform: translateY(30px);
5 animation: reveal-up linear both;
6 animation-timeline: view();
7 animation-range: entry 10% entry 90%;
8}
9
10@keyframes reveal-up {
11 from {
12 opacity: 0;
13 transform: translateY(30px);
14 }
15 to {
16 opacity: 1;
17 transform: translateY(0);
18 }
19}
20
21/* Slide from left */
22.reveal-left {
23 opacity: 0;
24 transform: translateX(-40px);
25 animation: reveal-from-left linear both;
26 animation-timeline: view();
27 animation-range: entry 10% entry 90%;
28}
29
30@keyframes reveal-from-left {
31 from {
32 opacity: 0;
33 transform: translateX(-40px);
34 }
35 to {
36 opacity: 1;
37 transform: translateX(0);
38 }
39}
40
41/* Scale up reveal */
42.reveal-scale {
43 opacity: 0;
44 transform: scale(0.9);
45 animation: reveal-scale-up linear both;
46 animation-timeline: view();
47 animation-range: entry 5% entry 85%;
48}
49
50@keyframes reveal-scale-up {
51 from {
52 opacity: 0;
53 transform: scale(0.9);
54 }
55 to {
56 opacity: 1;
57 transform: scale(1);
58 }
59}
60
61/* Staggered children — use animation-delay with view() */
62.stagger-list > * {
63 animation: reveal-up linear both;
64 animation-timeline: view();
65 animation-range: entry 10% entry 90%;
66}
67
68.stagger-list > *:nth-child(1) { animation-delay: 0ms; }
69.stagger-list > *:nth-child(2) { animation-delay: 50ms; }
70.stagger-list > *:nth-child(3) { animation-delay: 100ms; }
71.stagger-list > *:nth-child(4) { animation-delay: 150ms; }
72.stagger-list > *:nth-child(5) { animation-delay: 200ms; }
preview
Performance Considerations

Scroll-driven animations are designed for performance, but there are important guidelines to follow to keep your animations smooth at 60fps.

Only animate transform and opacity — these are GPU-composited and skip the paint/layout phases
Avoid animating top, left, width, height, or margin — these trigger expensive layout recalculation
Use will-change: transform, opacity on animated elements to hint the browser for optimization
Keep the DOM tree shallow for scroll-timeline — the browser checks scroll position on every frame
Test on low-end devices — even GPU-composited animations can stutter with too many simultaneous animations
Use animation-range to limit when animations run — avoid animating off-screen elements
scroll-perf.css
CSS
1/* ✓ Good — GPU-composited */
2.reveal {
3 animation: fade-up linear both;
4 animation-timeline: view();
5 animation-range: entry 10% entry 90%;
6 will-change: transform, opacity;
7}
8
9@keyframes fade-up {
10 from { opacity: 0; transform: translateY(30px); }
11 to { opacity: 1; transform: translateY(0); }
12}
13
14/* ✗ Bad — triggers layout on every frame */
15.bad-reveal {
16 animation: slide-bad linear both;
17 animation-timeline: view();
18}
19
20@keyframes slide-bad {
21 from { margin-top: 100px; opacity: 0; }
22 to { margin-top: 0; opacity: 1; }
23}

warning

Animating top, left, margin, or width in scroll-driven animations will cause layout thrashing. Always prefer transform for movement and opacity for fading.
Browser Support

Scroll-Driven Animations have growing browser support. Chrome and Edge support the feature; Firefox and Safari are working on implementation.

BrowserSupportVersion
Chrome115+
Edge115+
FirefoxIn development
SafariIn development
$Blueprint — Engineering Documentation·Section ID: CSS-50·Revision: 1.0