🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
CSS MASTER CLASS /// VISUAL ENGINEERING /// LAYOUT DESIGN /// ANIMATION LAB /// CSS MASTER CLASS /// VISUAL ENGINEERING ///

Scroll-Driven Animations: Native Scroll-Linked Motion

Learn how animation-timeline: scroll() drives an animation's progress from scroll position, how animation-timeline: view() drives animation from an individual element's visibility for scroll-reveal effects, and why this replaces a historically main-thread-heavy JavaScript pattern.

Total XP: 0|💻 css XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

Scroll-Driven Animations

Native, scroll-linked motion.


🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Scroll-linked effects — progress bars, parallax, reveal-on-scroll — have historically required JavaScript computing animation state on every scroll event. CSS scroll-driven animations bring this capability natively into the browser.

1Scroll Position Replaces Elapsed Time

A standard CSS animation is driven by a time-based timeline: it starts, and its keyframes progress based on elapsed seconds, regardless of anything else happening on the page. animation-timeline: scroll() swaps that time-based driver for a scroll-position-based one: the animation's percentage progress now maps directly and instantly to how far the specified scroll container has been scrolled, from 0% at the top to 100% at the bottom (or whichever axis and range is configured).

This is a genuinely different kind of timeline, not just a new easing or trigger — the animation has no concept of 'time' at all in this mode; it's purely a function of scroll position, which is why scrolling up instantly reverses the animation's progress too, with no special handling required.

.progress-bar {
  animation: grow linear;
  animation-timeline: scroll();
}
localhost:3000
✓ Position-Driven ProgressThe progress bar's width now maps directly to scroll position — scrolling up instantly reverses it, with zero JavaScript involved.

2view(): Animating Based On An Element's Own Visibility

A distinct but related timeline type, animation-timeline: view(), instead derives progress from where the animated element itself currently sits relative to its scroll container's visible viewport — as the element scrolls into view, crosses through it, and eventually scrolls back out, the animation progresses through that same journey.

This is exactly the mechanism behind the extremely common 'fade and rise into view as you scroll to this section' effect, and the animation-range property lets you control precisely which portion of that entry/exit journey the animation should actually cover — for example, completing entirely within the first 40% of the element's entry into view, rather than continuing to animate throughout its entire time on screen.

.reveal {
  animation: fade-in linear;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}
localhost:3000
Element enters viewport → animation begins
40% into view → animation complete

3What This Replaces: A Main-Thread-Heavy JavaScript Pattern

Before scroll-driven animations existed natively, achieving these exact effects required a JavaScript scroll event listener (often combined with IntersectionObserver for visibility detection), manually computing a progress value on every single scroll tick and writing it either directly to a style property or to a CSS custom property consumed elsewhere. This runs continuously on the main thread throughout the entire scroll interaction, competing for time with everything else JavaScript needs to do.

Native scroll-driven animations let the browser's compositor handle this computation directly, without continuous main-thread JavaScript execution — connecting directly back to the GPU Acceleration lesson's theme of moving work off the CPU's critical path wherever possible, applied here specifically to scroll-linked effects.

/* The pattern this replaces */
window.addEventListener('scroll', () => {
  const progress = computeScrollProgress();
  el.style.setProperty('--progress', progress);
});
localhost:3000
✓ Off The Main ThreadNative scroll-driven animations avoid the continuous main-thread JavaScript execution the equivalent scroll-listener pattern required.

4Step-by-Step Breakdown

Scroll Position As The Animation Clock. A normal CSS animation runs on a time-based clock — seconds ticking forward regardless of what the user does. Scroll-driven animations replace that clock entirely: the animation's progress is driven directly by scroll position, natively, with zero JavaScript and zero scroll-event listener.

Replacing Time With Scroll Position. animation-timeline: scroll() tells an animation to derive its progress from a scroll container's position instead of elapsed time — 0% scrolled maps to the animation's 0% keyframe, 100% scrolled maps to 100%, and everything in between tracks proportionally and instantly as the user scrolls.

The Timeline Swap. What does animation-timeline: scroll() change about how an animation's progress is determined?

  • It only changes how fast the animation plays, not what drives it
  • The animation's progress is now driven by scroll position instead of elapsed time
  • It has no effect without additional JavaScript

view() Timeline: Animating Based On Visibility. A related but distinct timeline, animation-timeline: view(), drives an animation based on an element's own position within its scroll container's visible viewport — as the element enters, crosses, and exits view, the animation progresses, ideal for 'fade and rise in as you scroll to it' effects on individual elements.

scroll() vs view(). What's the key difference between animation-timeline: scroll() and animation-timeline: view()?

  • They're functionally identical, just different syntax
  • scroll() tracks the scroll container's overall position; view() tracks a specific element's own visibility within the viewport
  • view() is a JavaScript-only feature, not real CSS

Why This Replaces A Common JavaScript Pattern. Before scroll-driven animations, this exact effect required a JavaScript scroll event listener (often paired with IntersectionObserver) computing progress manually on every scroll tick and writing it to a style property — a continuous, main-thread-bound cost that native scroll-driven animations eliminate by letting the compositor handle it directly.

The JavaScript It Replaces. Why is native animation-timeline: scroll() generally better for performance than the equivalent JavaScript scroll-listener pattern it replaces?

  • Purely because it results in less code to write
  • The browser can compute and apply scroll-driven animation progress without the continuous main-thread JavaScript execution a scroll listener requires
  • There's no meaningful performance difference

Scroll-Driven Animation Mastery. You now know how to drive an animation's progress directly from scroll position with scroll(), how view() drives animation based on an individual element's own visibility for scroll-reveal effects, and why this native approach eliminates a common, main-thread-heavy JavaScript scroll-listener pattern entirely.

Keep The End State After The Animation Finishes. animation-fill-mode: forwards keeps an element in its final keyframe state instead of snapping back.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Scroll-Driven Animations Must Respect prefers-reduced-motion Like Any Other Animation

A scroll-linked reveal or parallax effect is still motion, and users who've indicated a preference for reduced motion should have these effects disabled or substantially minimized, exactly as covered in the dedicated prefers-reduced-motion lesson.

2Scroll-Driven Effects Should Never Be The Sole Mechanism For Revealing Essential Content

Content that only becomes visible or legible via a scroll-timeline animation risks being missed by users who scroll quickly, use assistive technology that doesn't trigger the same visual scroll behavior, or have animations disabled — essential content should remain accessible regardless of animation state.

SEO Implications

  • 1

    Native Scroll-Driven Animation Reduces JavaScript Execution Time During Scroll, Improving Responsiveness Metrics

    Eliminating a continuous scroll-event JavaScript handler reduces main-thread contention during scrolling, which can measurably improve interaction responsiveness metrics relevant to Core Web Vitals.

  • 2

    Scroll-Linked Effects Implemented Natively Avoid The Layout Thrashing Risk Common In Hand-Rolled JS Implementations

    A naive JavaScript scroll-progress implementation risks interleaved reads/writes causing layout thrashing (covered in the Reflow and Repaint lesson); the native CSS feature avoids this failure mode entirely by design.

Best Practices

Prefer Native animation-timeline Over A JavaScript Scroll Listener For Any New Scroll-Linked Effect

It eliminates an entire category of main-thread performance risk and layout-thrashing bugs that hand-rolled JavaScript implementations are prone to, wherever current browser support allows it.

Use animation-range To Deliberately Control Where Within An Element's Visibility A view() Animation Plays

Without it, the animation runs across the element's entire time in the viewport, which is often not the intended, more contained reveal effect a design actually calls for.

Frequent Bugs

THE BUG

A scroll-driven progress bar doesn't animate at all.

THE FIX

Confirm the animation has a keyframe defined and animation-timeline: scroll() is applied to the correct element (often the animated element needs to be inside, or explicitly reference, the intended scrolling container).

THE BUG

A scroll-reveal effect using view() plays across the element's entire time in the viewport instead of completing quickly as intended.

THE FIX

Add an explicit animation-range to constrain the animation to a specific, narrower portion of the element's entry into view.

Real-World Examples

A Native Reading-Progress Bar

An article page's top progress bar showing how far the reader has scrolled through the content, implemented entirely with animation-timeline: scroll() and zero JavaScript.

@keyframes grow-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.reading-progress {
  animation: grow-progress linear;
  animation-timeline: scroll(root);
  transform-origin: left;
}

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Forgetting to define keyframes for a scroll-timeline animation

@keyframes grow { from { width: 0%; } to { width: 100%; } } .bar { animation: grow linear; animation-timeline: scroll(); }

The Solution //

animation-timeline only changes what drives progress; you still need a standard @keyframes animation defined and referenced.

The Error //

Not setting animation-range, causing a view() animation to play across an unintentionally wide portion of visibility

.reveal { animation-timeline: view(); animation-range: entry 0% cover 40%; }

The Solution //

Explicitly set animation-range to control exactly when within the element's visibility the animation should play.

Lesson Glossary

[01]Scroll-Driven Animation

An animation whose progress is derived from scroll position.

Code Preview
animation-timeline: scroll()

[02]scroll()

A timeline function driven by a scroll container's position.

Code Preview
animation-timeline: scroll();

[03]view()

A timeline function driven by an element's own viewport visibility.

Code Preview
animation-timeline: view();

[04]animation-range

Controls which portion of a timeline an animation plays across.

Code Preview
entry 0% cover 40%

Continue Learning