CSS Scroll Animation Generator
Generate scroll-driven CSS animations with view() and scroll() timelines — copy @keyframes and fallback code instantly
Applied as .scroll-reveal
view() keywords: entry, cover, exit, contain + percentage
@keyframes scroll-reveal {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.scroll-reveal {
animation: scroll-reveal ease-out both;
animation-timeline: view(block);
animation-range: entry 0% entry 100%;
}Add this CSS to your stylesheet. Apply .scroll-reveal to any element you want to animate on scroll.
You want cards to fade in as they scroll into view — the classic “reveal on scroll” effect. You’ve been using IntersectionObserver with a .is-visible class toggle and a CSS transition. It works, but it’s JavaScript for what should be a CSS effect, the animation only fires once (no reverse on scroll-up), and you need to manage observer cleanup in SPAs. The CSS Scroll Timeline API ties @keyframes directly to scroll progress — forward on scroll-down, backward on scroll-up, zero JavaScript.
Why This Generator (Not the Scroll Timeline Reference)
PureDevTools has a CSS Scroll Timeline Reference that covers the full API spec (scroll-timeline, view-timeline, animation-range keywords). This tool is the companion generator: pick a preset (fade in, slide left/right/bottom, scale up, rotate), configure view() or scroll() timeline, adjust animation range, and get production CSS with an IntersectionObserver fallback for older browsers. Everything runs in your browser; no data is sent anywhere.
What Are CSS Scroll-Driven Animations?
CSS Scroll-Driven Animations is a native browser API that lets you link CSS @keyframes animations directly to scroll progress — no JavaScript required. Instead of firing once on a timer, the animation plays forward as you scroll down and backward as you scroll up.
Two timeline types power the API:
view()timeline — the animation progress is tied to how much an element is visible in the viewport (entering, covering, exiting). Best for “reveal on scroll” effects.scroll()timeline — the animation progress is tied to the overall scroll position of the page or a container. Best for parallax, progress bars, and sticky headers.
view() vs scroll() Timeline
| Timeline | Progress Source | Best For |
|---|---|---|
view() | Element’s visibility within scroll container | Fade-in, slide-in, scale-up on scroll |
scroll() | Total scroll distance of the container | Parallax, progress bars, sticky transformations |
The view() timeline is the most commonly used for typical “scroll reveal” effects where elements animate as they come into view. The scroll() timeline is used when you want to tie animation to the raw scroll position of the page.
How to Use This Tool
- Choose a preset — Start with one of the 6 presets: Fade In, Slide from Left, Slide from Right, Slide from Bottom, Scale Up, or Rotate on Scroll.
- Select timeline type — Choose
view()for element-visibility-driven animation orscroll()for page-scroll-driven animation. - Configure animation range — Set the start and end range keywords (e.g.
entry 0%toentry 100%for a full entry animation). - Enable animation properties — Toggle opacity, translate X/Y, scale, and rotate. Set the “from” and “to” values for each enabled property.
- Adjust easing and fill mode — Choose from linear, ease, ease-in, ease-out, or ease-in-out. Fill mode controls whether values persist after the animation ends.
- Preview scroll progress — Drag the scroll progress slider (0–100%) to preview the animation at any point during the scroll.
- Copy the CSS — Click Copy on either the main CSS block or the fallback to add both to your project.
Generated CSS Structure
@keyframes scroll-reveal {
from {
opacity: 0;
transform: translateX(-60px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.scroll-reveal {
animation: scroll-reveal ease-out both;
animation-timeline: view(block);
animation-range: entry 0% entry 100%;
}
The tool also generates an @supports not (animation-timeline: view()) fallback CSS block using a CSS transition + .is-visible class, and a JavaScript IntersectionObserver snippet to apply the class when unsupported browsers enter the viewport.
Animation Range Keywords
For view() timelines, the range uses keywords that describe the element’s position relative to the scroll container:
| Keyword | Meaning |
|---|---|
entry 0% | The moment the element starts entering the viewport (bottom edge) |
entry 100% | The element has fully entered — top edge is visible |
cover 0% | The element first covers any part of the viewport |
cover 100% | The element covers the entire viewport |
exit 0% | The element starts leaving the viewport (top edge at viewport bottom) |
exit 100% | The element has fully left the viewport |
The most common range for reveal effects is entry 0% to entry 100% — the animation runs from the instant the element bottom enters the viewport until the entire element is visible.
Common Scroll Animation Patterns
Fade in on scroll (view-driven)
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-on-scroll {
animation: fade-in ease-out both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
Slide from left (view-driven)
@keyframes slide-left {
from { opacity: 0; transform: translateX(-60px); }
to { opacity: 1; transform: translateX(0); }
}
.slide-on-scroll {
animation: slide-left ease-out both;
animation-timeline: view(block);
animation-range: entry 0% entry 100%;
}
Scale up on scroll
@keyframes scale-up {
from { opacity: 0; transform: scale(0.7); }
to { opacity: 1; transform: scale(1); }
}
.scale-on-scroll {
animation: scale-up ease-out both;
animation-timeline: view();
animation-range: entry 0% entry 75%;
}
Page scroll progress bar (scroll-driven)
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress-bar {
transform-origin: left;
animation: progress linear both;
animation-timeline: scroll(block);
}
fill-mode Explained
The animation-fill-mode (shortened to fill mode in the shorthand) controls what happens when the element is outside the animation range:
| Value | Before Range | After Range |
|---|---|---|
none | Animation values not applied | Animation values not applied |
forwards | Default styles apply | Final keyframe values persist |
backwards | First keyframe values apply | Default styles apply |
both | First keyframe values apply | Final keyframe values persist |
For scroll-reveal effects, use both — the element starts invisible (from state) before scrolling and stays visible (to state) after it has fully entered.
Intersection Observer Fallback
The CSS Scroll Timeline API has broad support in Chrome 115+, Edge 115+, and Safari 18+. For older browsers, this tool generates a complete fallback:
@supports not (animation-timeline: view()) {
.scroll-reveal {
opacity: 0;
transform: translateX(-60px);
transition: opacity 0.6s ease-out, transform 0.7s ease-out;
}
.scroll-reveal.is-visible {
opacity: 1;
transform: translateX(0);
}
}
(function () {
if (CSS.supports('animation-timeline', 'view()')) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
}
});
},
{ threshold: 0.15 }
);
document.querySelectorAll('.scroll-reveal').forEach((el) => observer.observe(el));
})();
Because the fallback uses @supports not (animation-timeline: view()), browsers with native support use the scroll-driven animation, and older browsers fall back to the transition-based version automatically.
Browser Support
| Browser | CSS Scroll-Driven Animations | Version |
|---|---|---|
| Chrome | Supported | 115+ |
| Edge | Supported | 115+ |
| Firefox | Supported | 110+ (behind flag until 132) |
| Safari | Supported | 18.0+ |
| iOS Safari | Supported | 18.0+ |
For production use, always include the Intersection Observer fallback to ensure the animation works on older browsers and Firefox versions below 132.
Frequently Asked Questions
What is the difference between animation-timeline: view() and scroll()?
view() ties the animation to how much of a specific element is visible in the scroll container. The animation plays as the element enters and exits the viewport. scroll() ties the animation to the raw scroll distance of the container — 0% = top of page, 100% = bottom of page.
Why does my scroll animation not play smoothly?
Make sure animation-timeline is set and animation duration is set to auto or omitted from the shorthand. Scroll-driven animations don’t use a time-based duration — the scroll position controls the progress. This tool handles this correctly.
Can I use scroll-driven animations without any JavaScript? Yes. The CSS Scroll Timeline API is entirely CSS-based. No JavaScript is needed for supported browsers. This tool also generates an IntersectionObserver JavaScript fallback for older browsers.
Do I still need will-change: transform for performance?
Adding will-change: transform or will-change: opacity can improve compositing performance on elements that animate on scroll, but it’s not required. Use it on elements that are visually complex or part of a busy layout.
Why does fill-mode: both matter for scroll animations?
Without fill-mode: both, an element that has a scroll animation with opacity from 0 to 1 will be visible (opacity 1) before it enters the range — because the animation hasn’t started yet. With fill-mode: both, the element holds the first keyframe value (opacity: 0) before the range starts and holds the last keyframe value (opacity: 1) after the range ends.
Is my data sent to a server? No. All CSS generation, animation preview, and code formatting happen entirely in your browser. No data is transmitted anywhere and nothing is tracked.