Starting Exploration of Scroll-driven Animations in CSS

Posted on August 20, 2023
Takes about 6 minutes to read

CSS Scroll-driven Animations has recently made its debut on the main stage in the latest versions of Chrome and Edge. Before this module became available, linking an element's animation to a scroll position was only possible through JavaScript. I've been (and still am) a huge fan of GSAP ScrollTrigger as one way to achieve such an effect. I never imagined it would become a reality in CSS, but this new API lets us hook right into CSS animation @keyframes and scrub through the animation progress as we scroll the page.

My article will share demos and some early learnings about scroll-driven animations. If it's all new to you as well, I urge you to read Animate elements on scroll with Scroll-driven animations by Bramus and Michelle Barker's Scroll progress animations in CSS. They are both excellent deep dives into this new spec and helped me get a handle on how it works.

I had the chance to noodle around with both timeline types introduced in the Scroll-driven Animations spec:

When getting started, these progress visualizer tools were immensely helpful. They were frequently referenced while I tinkered on scroll timeline animation ideas.

Experiment #1: Photo figures

I turned to a recent CodePen Challenge to begin my exploration, which leans into View Progress Timeline features. When scrolling the page in the Photo figures CodePen demo, notice that the heading text follows down as it fades out, the first three Polaroid-style photos have a "develop" effect, and the last stack of photos shuffle between themselves.

Scrolling through the Photo figures CodePen demo

To make this happen, the way we write animation @keyframes hasn't changed. Instead, when applying that animation to an element, we introduce two new properties: animation-timeline and animation-range. Here's the simplified HTML for each "developing" photo as an example:

    <img class="develop-photo" />

And the CSS for its scroll-driven animation:

figure {
  view-timeline-name: --photo;

.develop-photo {
  animation: linear develop both;
  animation-timeline: --photo;
  animation-range: entry 30% cover 40%;

@keyframes develop {
  from {
    filter: blur(30px);
    scale: 1.1;
    opacity: 0;
  to {
    filter: blur(0);
    scale: 1;
    opacity: 1;

When applying animations to the heading and shuffling photos, declaring animation-timeline: view() with an animation-range were the magic ingredients to enable scrubbing through animation progress on scroll. The view() function binds the animation to the element as it appears in the viewport on the block axis. This function takes two parameters:

Since the default values are block and auto respectively, they can be omitted here.

Back to the example code above, I initially attemped to use the view() function on the "developing" photos but had no success. It seems that wrapping the img inside a div may be the reason—I believe that the overflow: hidden rule on the div now makes it the nearest scroll container for the img element. To get this photo animation working, setting view-timeline-name on the parent figure and then referencing it via animation-timeline ended up being the solution.

As for my chosen animation-range values? That was the result of much experimentation, playing with different combinations. I'm still getting the hang of it, but the Ranges and Progress Animation Visualizer Tool proved to be a crucial guide on my journey.

Additional notes

When working with these new animation properties, there are a few important bits to keep in mind:

Experiment #2: Weather app prototype

What excites me the most about scroll-driven animations is that it provides us the power to pull off some native-specific animation techniques directly in CSS. For example: the iOS weather app has been part of my daily ritual for quite some time. A lot of the app's animations are perfect for Scroll Progress Timeline! Check out my Weather app prototype on CodePen.

Scrolling through the Weather app prototype on CodePen

As explained in the previous demo, the animation-range property seems to be very versatile. I've only just scratched the surface of what it can do. In my first attempt to set an animation-range on the intro text fades, I used percentage values. Unfortunately, those animations would become slightly misaligned as the scroll container changed in height. In retrospect, that makes sense but, at the time, I had not realized that any <length-percentage> is a valid animation-range value. Once I switched from percentages to rem units, my animations lined up as expected, regardless of the scroll container height.

Scroll-driven for more

Something that I dig about both of these demos? The scroll timeline magic acts as a progressive enhancement. That won't always be the case, but it's awesome to see that both demos work as expected without it.

It has been such a thrill being introduced to the delight that is CSS Scroll-driven Animations. With this and View Transitions API on the horizon, it seems that simulating native app animation behaviors on the web is upon us. Maybe we'll soon see the end of companies constantly nudging us to download their native apps while we're browing their web app? Maybe they'll let us navigate around their home on the web as we intended without interruption?

Dream big.

Helpful resources

Back to all blog posts