On this page
CSS Scroll-Driven Animations: animation-timeline, scroll() and view()
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()orscroll-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()orview-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: --pageon a scroller, orview-timeline-name: --cardon a subject, thenanimation-timeline: --pageelsewhere. Usetimeline-scopeto 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,opacityandclip-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
animationshorthand afteranimation-timeline. The shorthand resets the timeline, so declareanimation-timelineafter it. - Forgetting
animation-fill-mode: both, so elements flash back to their original state outside the range. - Expecting time-based easing. Use
linearso the animation tracks scroll position exactly. - Using
overflow: hiddenon 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.
Press Run to execute.
Press Run to execute.
Press Run to execute.
Press Run to execute.
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.
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?