ShaharAmir
← Back to Blog
CSS3 min read

CSS Scroll-Driven Animations

Animate elements based on scroll position — no JavaScript needed

S
Shahar Amir

CSS Scroll-Driven Animations

Scroll animations used to need JavaScript. Now CSS can do it natively — and it's buttery smooth.

The Basics

Link any CSS animation to scroll progress:

css
123456789
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fade-in linear;
animation-timeline: view();
}

The card fades in as it enters the viewport!

Two Types of Timelines

1. Scroll Timeline

Progress based on scroll container position:

css
123456789
.progress-bar {
animation: grow-width linear;
animation-timeline: scroll();
}
@keyframes grow-width {
from { width: 0%; }
to { width: 100%; }
}

2. View Timeline

Progress based on element visibility in viewport:

css
1234
.reveal {
animation: slide-in linear;
animation-timeline: view();
}

Controlling the Range

Define when animation starts and ends:

css
1234567
.card {
animation: fade-in linear;
animation-timeline: view();
/* Start when element enters, end when fully visible */
animation-range: entry 0% entry 100%;
}

Range Keywords

css
1234567891011121314
/* When element enters viewport */
animation-range: entry;
/* When element exits viewport */
animation-range: exit;
/* When element is fully contained */
animation-range: contain;
/* When element covers the viewport */
animation-range: cover;
/* Custom range */
animation-range: entry 25% cover 50%;

Parallax Effect

css
123456789
.hero-image {
animation: parallax linear;
animation-timeline: scroll();
}
@keyframes parallax {
from { transform: translateY(-20%); }
to { transform: translateY(20%); }
}

Progress Indicator

css
123456789101112131415
.reading-progress {
position: fixed;
top: 0;
left: 0;
height: 3px;
background: var(--accent);
transform-origin: left;
animation: scale-x linear;
animation-timeline: scroll();
}
@keyframes scale-x {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}

Reveal on Scroll

css
12345678910111213141516
.section {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 10% cover 30%;
}
@keyframes reveal {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

Named Scroll Timelines

Animate based on a specific container's scroll:

css
12345678910
.scroll-container {
overflow: auto;
scroll-timeline-name: --my-timeline;
scroll-timeline-axis: block;
}
.animated-child {
animation: slide linear;
animation-timeline: --my-timeline;
}

The JavaScript Alternative

Before, you needed this:

javascript
12345
// Old way - heavy, janky
window.addEventListener("scroll", () => {
const progress = window.scrollY / maxScroll;
element.style.transform = `translateX(${progress * 100}%)`;
});

Now CSS handles it automatically with 60fps performance.

Browser Support

Chrome, Edge, and Firefox support it. Safari is catching up. Use feature detection:

css
123456
@supports (animation-timeline: view()) {
.card {
animation: fade-in linear;
animation-timeline: view();
}
}

Why It Matters

  • Performance — runs on compositor, not main thread
  • No JavaScript — simpler code
  • Declarative — CSS describes what, browser handles how

Scroll-driven animations are the future of scroll effects.

#animations#scroll#modern-css#performance

Stay Updated 📬

Get the latest tips and tutorials delivered to your inbox. No spam, unsubscribe anytime.