Scroll-Based Animations
Lesson 30 โข Advanced Track
What You'll Learn
๐ก Think of It Like This
Imagine a flipbook โ as you flip pages (scroll), the animation progresses. Scroll-driven animations tie CSS animations to your scroll position instead of a clock. The faster you scroll, the faster the animation plays. IntersectionObserver works differently โ it's like a motion sensor on a door that triggers once when someone walks through.
Understanding Scroll Animations
There are three main approaches to animating content based on scroll position. The best choice depends on browser support requirements, the type of effect you want, and performance considerations.
CSS Scroll-Driven Animations (the newest approach) let you bind any CSS animation directly to the scroll position using animation-timeline: scroll(). The animation progress maps 1:1 with how far the user has scrolled. This is ideal for progress bars, parallax backgrounds, and scroll-linked colour changes โ but browser support is still limited to Chromium browsers.
IntersectionObserver is the established cross-browser solution for "reveal on scroll" effects. It fires a callback when an element enters or exits the viewport, letting you toggle CSS classes. It's the industry standard for lazy loading images, fade-in animations, and infinite scroll pagination.
Approach Comparison
| Method | Browser Support | Thread | Best For |
|---|---|---|---|
| animation-timeline: scroll() | Chrome 115+ | Compositor | Progress bars, parallax, scroll-linked effects |
| animation-timeline: view() | Chrome 115+ | Compositor | Element-entry animations, scroll reveals |
| IntersectionObserver | All modern | Main (async) | Reveal-on-scroll, lazy load, infinite scroll |
| scroll event + rAF | Universal | Main | Fine-grained control (last resort) |
1. Scroll-Driven Progress Bar (CSS)
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; margin: 0; }
.progress-bar {
position: fixed; top: 0; left: 0; height: 4px;
background: linear-gradient(90deg, #F44336, #FF9800, #4CAF50, #2196F3);
width: 0%;
z-index: 1000;
animation: grow-progress auto linear;
animation-timeline: scroll();
}
@keyframes grow-progress { to { width: 100%; } }
.hero {
... How scroll() works: The animation-timeline: scroll() property replaces the default time-based timeline. At 0% scroll, the animation is at its from state. At 100% scroll, it reaches to. The animation-duration is ignored (set to auto) because progress is controlled by scroll position, not time.
2. Reveal on Scroll (IntersectionObserver)
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 0; margin: 0; }
.spacer { height: 50vh; display: flex; align-items: center; justify-content: center;
background: #f5f5f5; color: #999; font-size: 1.2rem; }
.reveal {
opacity: 0; transform: translateY(40px);
transition: opacity 0.6s ease, transform 0.6s ease;
max-width: 600px; margin: 40px auto; padding: 24px;
background:
...Step-by-Step: IntersectionObserver
Step 1: Set elements to opacity: 0 and transform: translateY(40px) by default.
Step 2: Add a .visible class that resets opacity and transform with a transition.
Step 3: Create an observer with a threshold (0.15 = trigger when 15% visible).
Step 4: When the observer fires, add the .visible class. The CSS transition handles the animation smoothly.
3. View Timeline Element Animations (CSS)
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 0; }
.spacer { height: 80vh; display: flex; align-items: center; justify-content: center; background: #f0f0f0; color: #999; font-size: 1.2rem; }
.animate-card {
max-width: 500px; margin: 60px auto; padding: 32px;
background: white; border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
animation: slide-in auto ease-
... view() vs scroll(): scroll() ties to the overall page scroll (0-100%). view() ties to when a specific element enters and exits the viewport. Use animation-range: entry 0% entry 100% to control exactly when the animation starts and completes relative to the element's visibility.
4. CSS Parallax Effect
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; margin: 0; overflow-x: hidden; }
.parallax-section {
position: relative; height: 70vh; overflow: hidden;
display: flex; align-items: center; justify-content: center;
}
.parallax-bg {
position: absolute; inset: -20%; z-index: -1;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
animation: paralla
...๐ก When to Use This
- Progress indicators: Reading progress bars, step indicators, loading animations tied to scroll
- Content reveals: Fade-in sections, staggered card animations on landing pages
- Parallax: Background layers moving at different speeds for depth
- Lazy loading: IntersectionObserver to load images only when they enter the viewport
- Infinite scroll: Trigger data fetching when a sentinel element becomes visible
โ ๏ธ Common Mistakes
- Using scroll event listeners for simple reveals โ IntersectionObserver is more performant and simpler. Scroll listeners fire on every pixel of scroll movement.
- Forgetting browser support for animation-timeline โ As of 2024, only Chromium supports it. Always provide an IntersectionObserver fallback for Safari/Firefox users.
- Animating too many elements simultaneously โ Revealing 20+ elements at once causes frame drops. Stagger entries using
transition-delayor threshold differences. - Not using
unobserve()after reveal โ If you only want a one-time reveal, callobserver.unobserve(entry.target)after adding the visible class to free resources. - Missing
@media (prefers-reduced-motion)โ Some users have motion sensitivity. Disable or simplify scroll animations when reduced motion is preferred. - Using
will-changeon every animated element โ Only use it on elements that actually need GPU promotion. Each promoted layer consumes GPU memory.
๐ Lesson Complete
- โ
animation-timeline: scroll()binds animation to scroll position - โ
animation-timeline: view()triggers when an element enters/exits viewport - โ
animation-rangecontrols exactly when the animation starts and ends - โ IntersectionObserver is the cross-browser standard for scroll reveals
- โ
Use
thresholdto control when elements trigger (0 to 1) - โ Prefer transform/opacity for jank-free scroll animations
- โ Parallax effects work by translating background layers at different scroll rates
- โ
Always respect
prefers-reduced-motionfor accessibility
Sign up for free to track which lessons you've completed and get learning reminders.