Have you ever visited a website where elements smoothly fade in, slide across, or scale up as you scroll down the page? These scroll-linked animations used to require JavaScript libraries, but now you can create them with pure CSS using the @scroll-timeline rule and animation-timeline property.
In this tutorial, I will show you how to create scroll-linked animations in CSS.
What Are CSS Scroll-Linked Animations?
Scroll-linked animations in CSS are visual effects that trigger based on your scrolling position. Instead of playing automatically when the page loads, these animations respond to how far you've scrolled. Think of elements that appear gradually as they enter your viewport or images that zoom in as you scroll past them.
In my experience, these animations make websites feel more dynamic and engaging without overwhelming your visitors. The best part? With modern CSS, you don't need any JavaScript to make them work.
Understanding the Scroll-Linked Animation Basics
Before we dive into scroll animations, you need to understand how regular CSS animations work. An animation has keyframes that define what changes happen, like moving from invisible to visible or from small to large.
Here's where scroll-linked animations differ: instead of running based on time (like "complete this animation in 2 seconds"), they run based on scroll position. When you scroll down 50% of the page, the animation is 50% complete.
The Core CSS Properties for Scroll-Linked Animation
You'll work with two main CSS features:
@scroll-timeline: This creates a timeline based on scrolling. You define which scrollable container to track and in which direction (vertical or horizontal).
animation-timeline: This property connects your animation to the scroll timeline instead of running on a time-based timeline.
How to Use @scroll-timeline
In CSS, the @scroll-timeline rule lets you create a named scroll timeline that you can reference later. Here's the basic syntax:
@scroll-timeline my-scroller {
source: auto;
orientation: block;
scroll-offsets: 0%, 100%;
}In this code, my-scroller is the name you give to your timeline. The source: auto means it will track the nearest scrollable ancestor, and orientation: block specifies vertical scrolling (use inline for horizontal).
Then you connect this timeline to an animation:
.animated-element {
animation: fadeIn linear;
animation-timeline: my-scroller;
}This approach gives you more control, especially when you want multiple elements to animate based on a specific container's scroll position.
Using animation-timeline with Built-in Functions
Instead of manually creating a @scroll-timeline, you can use built-in functions directly with animation-timeline. This is much simpler and what I use most often:
.element {
animation: slideIn linear;
animation-timeline: scroll();
}The scroll() function creates an automatic timeline based on the nearest scrollable ancestor. You can also use:
.element {
animation: fadeIn linear;
animation-timeline: view();
}The view() function is different. It creates a timeline based on when the element enters and exits the viewport. This means the animation starts when the element becomes visible and completes when it's fully in view.
Let me show you these in action with practical examples.
Example 1: Scroll-Linked Progress Bar Using Named Scroll Timeline in CSS
Let's start with a pure named scroll timeline example where we create a progress bar that grows as you scroll through content:
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 20px;
background: #0f172a;
font-family: system-ui, sans-serif;
}
h1 {
color: white;
text-align: center;
margin-bottom: 10px;
}
.instruction {
color: #94a3b8;
text-align: center;
margin-bottom: 20px;
}
.scroll-container {
width: 90%;
max-width: 600px;
height: 400px;
margin: 40px auto;
overflow-y: scroll;
background: white;
border-radius: 16px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
scroll-timeline-name: --container-timeline;
scroll-timeline-axis: block;
position: relative;
}
.progress-bar {
position: sticky;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(to right, #3b82f6, #8b5cf6, #ec4899);
transform-origin: left;
border-radius: 3px;
margin-bottom: 20px;
animation: growBar linear;
animation-timeline: --container-timeline;
z-index: 10;
}
@keyframes growBar {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
.content {
line-height: 1.8;
color: #475569;
}
.content h2 {
color: #1e293b;
margin-top: 0;
}
.content p {
margin-bottom: 16px;
}
</style>
</head>
<body>
<h1>Named Scroll Timeline Demo</h1>
<p class="instruction">Scroll inside the white box and watch the progress bar</p>
<div class="scroll-container">
<div class="progress-bar"></div>
<div class="content">
<h2>Scroll Progress Indicator</h2>
<p>As you scroll through this container, the colorful bar above grows from left to right. This demonstrates using a named scroll timeline with scroll-timeline-name property.</p>
<p>The timeline is tied specifically to this scrollable container, not the entire page. This gives you precise control over which scroll area triggers your animations.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis.</p>
<p>Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
<p>Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt.</p>
<p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores.</p>
<p>When you reach the end of this content, the progress bar will be completely filled, showing you've scrolled through the entire container!</p>
</div>
</div>
</body>
</html>
Live Preview:
See the Pen sl-anim-1 by Vinish Kapoor (@foxinfotech) on CodePen.
In this example, I created a named timeline using scroll-timeline-name: --container-timeline and scroll-timeline-axis: block properties on the scrollable container itself. The double dash prefix (--) is required for named timelines. The progress bar stays at the top using position: sticky and grows horizontally as you scroll through the content.
The transform: scaleX(0) makes the bar start with zero width, and as you scroll, it expands to full width with scaleX(1). This creates a visual indicator of your scroll progress through that specific container, which is a common pattern I use for long-form content.
Example 2: Fade-In Effect (Scroll-Linked Animation with view())
Let's create a simple fade-in effect for a box as you scroll down:
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 20px;
min-height: 200vh;
background: linear-gradient(to bottom, #1e3a8a, #3b82f6);
}
.fade-box {
width: 300px;
height: 200px;
background: white;
margin: 600px auto 0;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
color: #1e3a8a;
animation: fadeIn linear;
animation-timeline: view();
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body>
<h1 style="color: white; text-align: center;">Scroll Down</h1>
<div class="fade-box">I Fade In!</div>
</body>
</html>
Live Preview:
See the Pen sc-anim-2 by Vinish Kapoor (@foxinfotech) on CodePen.
In this example, I'm using animation-timeline: view(), which creates an automatic scroll timeline based on the element's visibility in the viewport. The animation starts when the element enters the viewport and completes when it's fully visible. You don't need to write a separate @scroll-timeline rule because view() handles it automatically.
The @keyframes fadeIn rule defines what happens: the box starts invisible (opacity: 0) and slightly below its final position (translateY(50px)), then transitions to fully visible at its natural position.
Example 3: Scale on Scroll (Scroll-Linked Animation Using CSS)
Now let's create something that grows as you scroll:
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 40px;
min-height: 200vh;
background: #0f172a;
}
.scale-container {
margin-top: 400px;
text-align: center;
}
.scale-box {
width: 200px;
height: 200px;
background: linear-gradient(135deg, #ec4899, #8b5cf6);
margin: 0 auto;
border-radius: 20px;
animation: scaleUp linear;
animation-timeline: view();
}
@keyframes scaleUp {
from {
transform: scale(0.5) rotate(0deg);
opacity: 0.5;
}
to {
transform: scale(1) rotate(360deg);
opacity: 1;
}
}
h2 {
color: white;
margin-bottom: 30px;
}
</style>
</head>
<body>
<h1 style="color: white; text-align: center;">Scroll to See Magic</h1>
<div class="scale-container">
<h2>Watch Me Grow</h2>
<div class="scale-box"></div>
</div>
</body>
</html>
Live Preview:
See the Pen sc-anim-3 by Vinish Kapoor (@foxinfotech) on CodePen.
Here, the animation combines multiple effects. The transform: scale(0.5) makes the box start at half its size, and rotate(0deg) begins with no rotation. As you scroll, it grows to full size (scale(1)) while rotating 360 degrees. I find combining transformations like this creates more interesting visual effects.
When to Use Named Timelines vs Built-in Functions
You might wonder when to use the named timeline approach versus the simpler scroll() or view() functions. Here's my recommendation:
Use named timelines (with scroll-timeline-name) when you need to animate based on a specific scrollable container's position, especially if multiple elements need to reference the same timeline. This gives you precise control.
Use scroll() for simple page-wide effects like progress bars that track the entire document scroll.
Use view() when you want elements to animate as they enter and exit the viewport, which is perfect for reveal animations.
Browser Support Note
I must mention that scroll-linked animations in CSS are relatively new. As of now, they work in Chrome, Edge, and other Chromium-based browsers. Firefox and Safari are working on support. For production websites, you might want to check browser compatibility and consider fallbacks for unsupported browsers.
Wrapping Up
CSS Scroll-linked animations open up creative possibilities for modern web design. You can create engaging experiences without relying on JavaScript libraries. Start simple with fade-ins and grow from there. The examples I've shared should give you a solid foundation to experiment with your own scroll-based effects.
See also:


