CSS Scroll-Driven Animation Generator

Generate CSS scroll-driven animations — the native browser feature that links a CSS animation's progress to scroll position, with no JavaScript event listeners. Choose between scroll-timeline (tracks page or container scroll) and view-timeline (tracks an element entering the viewport), pick an animation style, and get the complete CSS including @keyframes and animation-range.

How to use the CSS Scroll-Driven Animation Generator

1. Choose the timeline type: scroll-progress links the animation to the page or container scroll position (progress goes 0%→100% as you scroll from top to bottom); element-reveal links the animation to an element entering and exiting the viewport (reveal-on-scroll). 2. Choose the animation preset. 3. Set the animation range — this defines which slice of the scroll range drives the animation. For view-timeline, entry 0% cover 40% means the animation runs from when the element first enters the viewport until it covers 40% of the viewport. 4. Choose the scroll axis. 5. Click Generate and copy the CSS. Apply the .scroll-animated class (or the generated selector) to the elements you want to animate. Scroll-driven animations are progressive enhancement — add @supports (animation-timeline: scroll()) to scope them.

CSS scroll-driven animations — how animation-timeline works

CSS scroll-driven animations (CSS Animation Level 2 / Scroll-driven Animations spec) replace JavaScript IntersectionObserver + class-toggle patterns with a pure-CSS equivalent. The key property is animation-timeline: set to scroll() to use the nearest scrollable ancestor (or the page), or to view() to use the element's visibility in the viewport. The browser then maps the animation's progress to the scroll progress rather than wall-clock time.

animation-range restricts which portion of the scroll range drives the animation. For view() timelines, the range keywords correspond to phases of the element's entry/exit cycle: entry (element starts entering viewport), cover (element fully covers the relevant axis), exit (element starts leaving). Combining range start and end lets you trigger an animation precisely — for example entry 0% cover 30% animates only while the element is scrolling into view, then freezes.

Because the animation is driven by scroll position rather than time, it scrubs perfectly with the user's scroll — including backwards. There is no need to reset state or debounce scroll events. Scroll-driven animations are supported in Chrome 115+, Edge 115+, and Firefox 110+ (behind flag until Firefox 130). Use @supports (animation-timeline: scroll()) as a feature guard and provide a non-animated fallback for Safari (as of mid-2024, not yet supported there).

Common use cases

  • Reveal on scroll — replace IntersectionObserver + classList.add("visible") with a view() animation-timeline that fades or slides content in as it enters the viewport.
  • Reading progress bar — animate a fixed top bar from width 0% to 100% with animation-timeline: scroll(root) to show reading progress with three lines of CSS.
  • Parallax effects — link translateY to a scroll-timeline to create performant parallax without JS, running on the compositor thread.
  • Sticky header fade — reduce a header's height or opacity as the user scrolls past the hero section using a scroll-timeline anchored to the hero element.
  • Sequential section reveals — stagger multiple elements entering view with different animation-delay values while sharing the same view() timeline.

Frequently asked questions

Do scroll-driven animations work in Safari?

Not yet as of mid-2024. Chrome 115+, Edge 115+, and Firefox 130+ support them. Use @supports (animation-timeline: scroll()) to scope the rules, so Safari users see the default (non-animated) state.

What is the difference between scroll() and view()?

scroll() ties the animation to a scrollable container's scroll progress — useful for progress bars and parallax. view() ties it to an element's position relative to the viewport — useful for reveal-on-scroll effects. view() is a shorthand for a named view-timeline.

Can I still use animation-duration with scroll-driven animations?

Yes, but the duration has no effect on the actual timing — it is overridden by the scroll position. However, you must still set a non-zero duration (or use animation-duration: auto) for the animation to apply. The output includes a comment about this.

What is animation-range entry, cover, exit?

These are range-name keywords for view() timelines. entry: the phase when the element is entering the viewport. cover: when the element fully covers the scrollport axis. exit: when the element is leaving. You combine a range-name with a percentage to specify exact start/end points of the animation within that phase.

How do I scroll-drive an animation on a specific container, not the page?

Name the container with scroll-timeline-name: --my-scroll and reference it with animation-timeline: --my-scroll. Or use scroll(nearest) to automatically use the nearest scrollable ancestor without naming it.