Skip to main content
Syllabus
On this page

CSS Scroll-Driven Animations: animation-timeline, scroll() and view()

Badar KhalilUpdated September 30, 2026 5 min read

What are scroll-driven animations?

CSS scroll-driven animations link the progress of an animation to scrolling instead of time. Scroll down and the animation plays forward; scroll up and it plays backward. You get reading progress bars, reveal-on-scroll effects, parallax and sticky header changes using only CSS, with no scroll event listeners or IntersectionObserver. Because they can run off the main thread in supporting browsers, they are often smoother than JavaScript solutions.

Focus keyword: CSS scroll-driven animations. Related trending keywords: animation-timeline, scroll progress bar CSS, view() timeline, animation-range, scroll reveal animation without JavaScript, CSS parallax, scroll-timeline, view-timeline.

Two kinds of timelines

  • Scroll progress timeline: progress equals how far a scroll container has been scrolled (0% at the top, 100% at the bottom). Created with scroll() or scroll-timeline. Best for page progress bars and parallax.
  • View progress timeline: progress equals how much an element has moved through the scrollport (from entering to leaving). Created with view() or view-timeline. Best for reveal-on-scroll and per-element effects.

The core syntax

1) Write normal @keyframes. 2) Attach them with animation (duration is ignored by scroll timelines, but set linear easing in most cases). 3) Set animation-timeline.

  • animation-timeline: scroll(): nearest ancestor scroller. Arguments: scroller (nearest, root, self) and axis (block, inline, y, x). Example: scroll(root block).
  • animation-timeline: view(): progress based on the element's own visibility in the scrollport. You can add an axis and an inset.
  • Named timelines: scroll-timeline-name: --page on a scroller, or view-timeline-name: --card on a subject, then animation-timeline: --page elsewhere. Use timeline-scope to share a timeline with elements that are not descendants.

animation-range: choose when it plays

The animation-range property limits which part of the timeline drives the animation. For view timelines you can use named ranges:

  • entry: while the element is entering the scrollport.
  • exit: while it is leaving.
  • cover: from first touching to fully leaving.
  • contain: while fully visible inside the scrollport.

Example: animation-range: entry 0% entry 100%; makes a reveal finish exactly when the element is fully in view. You can also use animation-range: entry 10% cover 40%, or plain lengths and percentages.

Progressive enhancement and fallback

Support is excellent in Chromium browsers and is rolling out to others, so treat scroll-driven animation as an enhancement. Wrap it in @supports (animation-timeline: scroll()), and make sure the page works with no animation at all. A good rule: elements must be visible and usable by default, and only hidden (for example with opacity: 0) inside the @supports block. Check current browser support on Can I use before relying on it for essential UI.

Accessibility and performance

  • Wrap movement-heavy effects in @media (prefers-reduced-motion: no-preference).
  • Animate transform, opacity and clip-path. Avoid properties that trigger layout.
  • Avoid parallax that moves large areas quickly, because it can trigger motion sickness.
  • Do not tie critical information (like form errors) to scroll-only reveals.

Common mistakes

  • Setting the animation shorthand after animation-timeline. The shorthand resets the timeline, so declare animation-timeline after it.
  • Forgetting animation-fill-mode: both, so elements flash back to their original state outside the range.
  • Expecting time-based easing. Use linear so the animation tracks scroll position exactly.
  • Using overflow: hidden on the wrong ancestor, which makes it the nearest scroller instead of the page.

Quick summary

CSS scroll-driven animations use animation-timeline with scroll() or view() to bind keyframes to scrolling, and animation-range to choose when they play. Always add @supports and reduced-motion guards.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Exercise: Shrink a sticky header on scrollHTML

Use animation-timeline: scroll() to shrink the header padding from 24px to 8px and reduce its font size while the user scrolls the first 150px. Use animation-range with lengths, wrap it in @supports, and disable it for users who prefer reduced motion.

Try it Yourself HTML
Output

Press Run to execute.

Show expected output
As you scroll the first 150px, the sticky header smoothly becomes shorter with a smaller title. Browsers without support show the normal header.

This is a self-check — compare your result with the expected output above.

Was this page helpful?