Looking for Javascript solutions?
1. CSS Scroll-Driven Animations (The Game Changer)
As of December 2024, scroll-driven animations are finally here — no JavaScript, no dependencies, no libraries, just pure CSS. These animations run off the main thread, delivering smooth, GPU-accelerated experiences.
Key Features:
- Browser Support: Chrome as of December 2024. Firefox supports them, too, though you’ll need to enable a flag
- Performance: Runs on GPU, off the main thread
- Two Types: Scroll Progress Timeline and View Progress Timeline
2. View Progress Timeline (Perfect for Reveal Effects)
The view()
function is ideal for creating reveal animations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.reveal-element { animation: fadeIn linear both; animation-timeline: view(); animation-range: entry 25% cover 75%; } @keyframes fadeIn { from { opacity: 0; transform: translateY(100px); } to { opacity: 1; transform: translateY(0); } } |
Animation Range Options:
cover
: Full visibility trackingcontain
: When fully visibleentry
: Entering the viewportexit
: Leaving the viewportentry-crossing
: Crossing starting edgeexit-crossing
: Crossing ending edge
3. Scroll Progress Timeline
For progress indicators and scroll-linked effects:
1 2 3 4 5 6 7 8 9 10 11 12 |
.progress-bar { animation: grow linear; animation-timeline: scroll(); transform-origin: left; } @keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } } |
4. Modern Reveal Patterns
Fade and Slide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.reveal-slide { animation: slideUp linear both; animation-timeline: view(); animation-range: entry 0% cover 40%; } @keyframes slideUp { from { opacity: 0; transform: translateY(100px); } to { opacity: 1; transform: translateY(0); } } |
Scale Reveal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.reveal-scale { animation: scaleIn linear both; animation-timeline: view(); animation-range: entry 15% cover 50%; } @keyframes scaleIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } } |
Staggered Reveals
1 2 3 4 5 6 7 8 9 10 |
.reveal-item { animation: fadeInUp linear both; animation-timeline: view(); } .reveal-item:nth-child(1) { animation-range: entry 10% cover 50%; } .reveal-item:nth-child(2) { animation-range: entry 20% cover 60%; } .reveal-item:nth-child(3) { animation-range: entry 30% cover 70%; } |
5. Advanced Techniques
Parallax Effects
1 2 3 4 5 6 7 8 9 10 11 12 |
.parallax-element { animation: parallaxMove linear; animation-timeline: view(); animation-range: cover; } @keyframes parallaxMove { from { transform: translateY(0); } to { transform: translateY(-50px); } } |
Direction-Based Reveals
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@keyframes slideInOut { entry 0% { transform: translateX(-100px); opacity: 0; } entry 100% { transform: translateX(0); opacity: 1; } exit 0% { transform: translateX(0); opacity: 1; } exit 100% { transform: translateX(100px); opacity: 0; } } |
6. Named Timelines for Complex Animations
1 2 3 4 5 6 7 8 9 10 |
.scroll-container { scroll-timeline: --myScroller block; } .animated-element { animation: reveal linear; animation-timeline: --myScroller; } |
7. Best Practices
Progressive Enhancement
1 2 3 4 5 6 7 8 |
@supports (animation-timeline: view()) { .reveal { animation: fadeIn linear both; animation-timeline: view(); } } |
Accessibility
1 2 3 4 5 6 7 8 9 10 |
@media (prefers-reduced-motion: no-preference) { @supports (animation-timeline: view()) { .reveal { animation: slideIn linear both; animation-timeline: view(); } } } |
8. Performance Tips
For the best results, stick to animating GPU-friendly properties like transforms, opacity, and some filters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* Good - GPU accelerated */ .optimized-reveal { animation: optimizedFade linear both; animation-timeline: view(); will-change: transform, opacity; } @keyframes optimizedFade { from { opacity: 0; transform: translate3d(0, 50px, 0); } to { opacity: 1; transform: translate3d(0, 0, 0); } } |
9. Polyfill for Broader Support
For browsers that don’t support scroll-driven animations yet, you can use the polyfill:
1 2 3 |
<script src="https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js"></script> |
10. Common Gotchas
- A scroll container is necessary for scroll-driven animations to work
- Using
overflow: hidden
can disrupt the scroll-seeking mechanism in scroll-driven animations. The recommended solution is to switch tooverflow: clip
- Always place
animation-timeline
after theanimation
shorthand property
Resources and Further Reading
- An Introduction To CSS Scroll-Driven Animations (Smashing Magazine)
- Scroll-Driven Animations Tools and Demos
- Chrome Developer Documentation
- MDN Web Docs – CSS Scroll-Driven Animations
- Unleash the Power of Scroll-Driven Animations (Video Course)
- Detect if an Element Can Scroll (Bramus)
- Scroll Timeline Polyfill (GitHub)
- A Practical Introduction to Scroll-Driven Animations (Codrops)
These pure CSS scroll-driven animations represent a significant advancement in web animation capabilities, offering performance benefits and cleaner code compared to JavaScript-based solutions. While browser support is still evolving, they’re ready for progressive enhancement in production environments.
FAQ
What are CSS scroll-driven animations?
How do I create a basic scroll-driven animation?
animation-timeline
property with the scroll()
function:
What browsers support scroll-driven animations?
What’s the difference between scroll() and view() timelines?
scroll()
for scroll-based progress bars and view()
for animating elements as they enter/exit the viewport.
How do I use animation-range to control when animations start and end?
animation-range
property controls where animations start and end. For scroll timelines, use length values:
For view timelines, use keywords like entry
, exit
, cover
, or contain
.
Why do scroll-driven animations perform better than JavaScript alternatives?
How do I create a reading progress bar with CSS?
Can I animate elements as they enter the viewport?
view()
timeline to animate elements based on their visibility:
What CSS properties should I animate for best performance?
How do I handle accessibility with scroll animations?
Can I use named timelines for multiple elements?
What’s the view-timeline-inset property for?
view-timeline-inset
adjusts when animations start/end relative to viewport edges. It creates a margin that changes the animation trigger points, useful for starting animations before elements fully enter the viewport.
Do I need to set animation-duration for scroll animations?
animation-duration: 1ms
. The actual duration doesn’t matter since the animation is driven by scroll, not time.
How do I debug scroll-driven animations?
Can I combine multiple animations with different ranges?
What common mistakes should I avoid?
overflow: clip
instead of hidden
when needed.