CSS Scroll-Driven Animations: 5 Pure-CSS Scroll Animation Patterns Without JavaScript
CSS Scroll-Driven Animations: Silky Smooth Scrolling Without JS
Parallax with JS scroll listeners causing jank, verbose IntersectionObserver code, requestAnimationFrame performance overhead — scroll animations have long depended on JavaScript. CSS Scroll-Driven Animations let you implement parallax, progress bars, and reveal animations purely in CSS: zero JS, 60fps, automatic GPU acceleration. In 2026, CSS scroll-driven animations are fully supported in Chrome and Safari.
This article covers 5 core patterns, guiding you through scroll-timeline → view-timeline → parallax → progress indicator → reveal animations.
Core Concepts
| Concept | Description |
|---|---|
| scroll-timeline | Animation timeline based on scroll position |
| view-timeline | Animation timeline based on element visibility |
| animation-timeline | Specifies which scroll timeline an animation uses |
| animation-range | Start and end positions of animation within scroll range |
| scroll() | Anonymous scroll timeline function |
| view() | Anonymous view timeline function |
| scroll-progress | Scroll progress 0-1 |
| view-progress | Visibility progress 0-1 |
Problem Analysis: 5 Major CSS Scroll Animation Challenges
- Browser compatibility: Firefox support lags behind
- Difficult debugging: DevTools scroll animation support is limited
- Nested scrolling: Non-document scroll container configuration is complex
- Animation range calculation: animation-range percentage has high understanding cost
- Fallback: Unsupported browsers need JS fallback
Step-by-Step: 5 CSS Scroll Animation Patterns
Pattern 1: scroll-timeline Scroll Progress Animation
/* Page top progress bar */
@keyframes progress-bar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, #6366f1, #ec4899);
transform-origin: left;
animation: progress-bar linear;
animation-timeline: scroll(root);
}
Pattern 2: view-timeline Viewport Reveal Animation
@keyframes reveal-up {
from {
opacity: 0;
transform: translateY(60px) scale(0.95);
filter: blur(4px);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
filter: blur(0);
}
}
.card {
animation: reveal-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 80%;
}
Pattern 3: Parallax Scrolling Effect
@keyframes parallax-slow {
from { transform: translateY(0); }
to { transform: translateY(-100px); }
}
@keyframes parallax-fast {
from { transform: translateY(0); }
to { transform: translateY(-300px); }
}
.parallax-bg {
animation: parallax-slow linear both;
animation-timeline: scroll(root);
}
.parallax-fg {
animation: parallax-fast linear both;
animation-timeline: scroll(root);
}
Pattern 4: Named Scroll Timeline
.scroller {
overflow-y: auto;
scroll-timeline-name: --my-scroller;
scroll-timeline-axis: block;
}
@keyframes rotate-icon {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.rotating-icon {
animation: rotate-icon linear;
animation-timeline: --my-scroller;
}
Pattern 5: Complex Animation Sequences
@keyframes hero-sequence {
0% {
opacity: 0;
transform: translateY(100px) scale(0.8);
filter: blur(10px);
}
30% {
opacity: 1;
transform: translateY(0) scale(1);
filter: blur(0);
}
70% {
opacity: 1;
transform: translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateY(-50px) scale(0.95);
}
}
.hero-title {
animation: hero-sequence linear both;
animation-timeline: view();
animation-range: entry 0% exit 100%;
}
Pitfall Guide
Pitfall 1: Not specifying animation-timeline
/* ❌ Wrong: missing animation-timeline */
.element {
animation: slide-in 1s linear;
/* Animation won't follow scroll */
}
/* ✅ Correct: specify scroll timeline */
.element {
animation: slide-in linear both;
animation-timeline: view();
}
Pitfall 2: Wrong animation-range values
/* ❌ Wrong: range exceeds scroll range */
.element {
animation-range: entry 0% entry 200%; /* 200% invalid */
}
/* ✅ Correct: use valid range */
.element {
animation-range: entry 0% entry 100%;
}
Pitfall 3: Transform conflicting with scroll
/* ✅ Correct: use will-change hint */
.parallax {
animation: parallax linear both;
animation-timeline: scroll(root);
will-change: transform;
}
Pitfall 4: Not handling fallback
/* ✅ Correct: provide fallback */
@supports (animation-timeline: view()) {
.card {
animation: reveal-up linear both;
animation-timeline: view();
}
}
@supports not (animation-timeline: view()) {
.card { opacity: 1; transform: none; }
}
Pitfall 5: Scroll container without overflow
/* ✅ Correct: ensure container is scrollable */
.scroller {
overflow-y: auto;
scroll-timeline-name: --my-scroll;
}
Error Troubleshooting
| # | Error | Cause | Solution |
|---|---|---|---|
| 1 | Animation doesn't follow scroll | No animation-timeline set | Add animation-timeline: view() or scroll() |
| 2 | Animation ends early | animation-range too small | Increase range |
| 3 | Wrong parallax direction | scroll-timeline-axis misconfigured | Use block/inline for direction |
| 4 | Named timeline invalid | scroll-timeline-name mismatch | Ensure parent-child names match |
| 5 | Animation jank | No will-change | Add will-change: transform |
| 6 | Firefox not working | Browser doesn't support | Use @supports for fallback |
| 7 | Nested scroll not working | Inner/outer scroll conflict | Use named scroll-timeline |
| 8 | Animation jumping | Keyframes not smooth enough | Add more keyframes or use linear |
| 9 | Fixed elements abnormal | transform affects fixed positioning | Move fixed elements outside transform container |
| 10 | Mobile not smooth | GPU acceleration not enabled | Add transform: translateZ(0) |
Advanced Optimization
- Scroll Snap + Scroll Animations: Combining paged scrolling with animations
- CSS Houdini custom properties: Register animation properties for complex effects
- prefers-reduced-motion: Respect user preference for reduced motion
- Container queries + scroll animations: Responsive and scroll animation synergy
- WAAPI + Scroll Timeline: Web Animations API combined with CSS scroll timelines
Comparison
| Dimension | CSS Scroll-Driven | JS IntersectionObserver | JS scroll event | GSAP ScrollTrigger |
|---|---|---|---|---|
| Performance | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| Code Volume | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| Browser Support | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Flexibility | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Debugging Ease | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Learning Curve | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
Summary: CSS scroll-driven animations let you achieve silky smooth scrolling effects with zero JS, far outperforming JS solutions. CSS scroll animations suit scroll interaction scenarios pursuing extreme performance, especially parallax, progress bars, and reveal animations. With full Chrome and Safari support in 2026, use with @supports fallback.
Online Tools
- JSON Formatter: /en/json/format
- Hash Calculator: /en/encode/hash
- cURL to Code: /en/dev/curl-to-code
Try these browser-local tools — no sign-up required →