CSS Scroll-Driven Animations: A Step-by-Step Guide With No JavaScript

Written by briantreese | Published 2024/02/16
Tech Story Tags: css | web-design | frontend-development | keyframe-animation | css-properties | scroll-behavior | scroll-driven-animations | css-guide

TLDRvia the TL;DR App

In this post, we’re going to look at a really exciting new CSS feature: scroll-driven animations. We’ll take some boring, scrolling content and upgrade it to something a little more interesting. And we’ll do it all with CSS—no JavaScript. Alright, let’s get to it!

https://youtu.be/PcyFYILv8TE?embedable=true

Ok, so here’s what we are starting with. We’ve got this content with some basic info about NBA teams. And, when we scroll down to see the list, it looks not only a little boring but almost broken. The headers are sticky, but before they stick to the top, they have extra space next to them. As they stick, they sit next to each other and look ok.

https://codepen.io/brianmtreese/pen/dyrMGBp?embedable=true

Wouldn’t it be great if we could transition these as we scroll? Well, we actually can with scroll-driven animations.

What are CSS Scroll-Driven animations?

Scroll-driven animations in CSS provide effects that used to require JavaScript to pull off. They allow us to animate items tied to their visibility within a given scroll container. And the possibilities are endless.

Using the CSS animation-timeline Property and view() Function

The first thing we want to do is animate our headers so that they look better as we scroll. To do this, we start by adding the animation-timeline property. This is the brand-new property that makes all of this possible.

It’s what allows us to bind animations to a scroll container as it scrolls. Now, there are many ways to use this property, but for this example, we are going to keep it simple.

.title {
    animation-timeline
}

We’re going to use the also brand new, view() function. This allows us to tie animations to the nearest parent scrolling container. Now, there are many different options we can pass to the view function, but we’re just going to pass it in the direction of the scroll that we want to animate along. In our case, it’s the block direction because that’s the way that our content scrolls. If it were scrolling horizontally, we could use inline instead.

.title {
    animation-timeline: view(block);
}

Ok, now let’s create a keyframe animation for how we want these headers to animate. What we’ll do is have the containers start at full width and animate to the final fifty percent width. So, let’s call it shrink. Ok, we’ll go from a width of one hundred percent to a width of fifty percent, but we also have a four-pixel gap between the headers when they’re pinned, so we’ll make it a calc and subtract half of that gap, 2px.

Let’s also scale from eighty percent to one hundred percent. And let’s also fade it in from an opacity of zero to an opacity of one.

@keyframes shrink {
    from {
        width: 100%;
        scale: 0.8;
        opacity: 0;
    }
    to {
        width: calc(50% - 2px);
        scale: 1;
        opacity: 1;
    }
}

Now, let’s add this animation to our element with the animation name property.

.title {
    ...
    animation-name: shrink;
}

Using the CSS animation-range Property

Ok, the last piece is to add an animation-range. With this property, we can define when we want our animation to begin and when we want it to end. This property is actually a shorthand for the new animation-range-start and animation-range-end values.

Now, this is a little tricky to understand, but we’ll give it a value of entry negative ten percent for the start and contain forty percent for the end.

.title {
    ...
    animation-range: entry -10% contain 40%;
}

Wait what? You can use this tool to explore what this means. This is a tool created by a developer relations engineer at Google. It allows us to easily see how the animation range settings work.

When you scroll in that example, you can see where our animation starts and ends while scrolling within the view. This tool really helped me understand how these settings work.

Ok, when we go back over to our example when we scroll down, we can see our animation is applied correctly. Much better.

https://codepen.io/brianmtreese/pen/PoLNNZR?embedable=true

And, if we scroll slowly, we can see how the animation gets applied. As it enters the view, it’s full width, and by the time it reaches about two-thirds of the scroll height, it’s nearly fifty percent wide, fully opaque, and fully scaled in. And if we stop scrolling anywhere in here, the items freeze at their current spot in the animation. I mean, to me, this is mind-blowing. It's crazy that this is all done with a few lines of CSS.

Adding Scroll Animations to the List Items

Now, this isn’t looking exactly how I want it yet. I want to animate the list items on scroll as well. For these items, I want to do the opposite of the titles. I want to start them smaller and grow them as they scroll in. So, we start by adding our animation-timeline again. We’ll use the view function again with a value of block.

li {  
    animation-timeline: view(block);
}

Now we add our new keyframe animation, let’s call it “grow” this time. We’ll start with a scale of eighty percent and an opacity of zero. And we’ll animate to a scale of one hundred percent and opacity of one.

@keyframes grow {
    from {
        scale: 0.8;
        opacity: 0;
    }
    to {
        scale: 1;
        opacity: 1;
    }
}

Now, we can add it with the animation name property.

li {
    ...
    animation-name: grow;
}

And, we’ll add our range. We’ll use the same value as our previous animation to start, entry negative ten percent, and to end, contain forty percent.

li {
    ...
    animation-range: entry -10% contain 40%;
}

Ok, so how does it look now?

https://codepen.io/brianmtreese/pen/rNReeLN?embedable=true

Nice, I like this effect. It looks a lot smoother than it did before the animation.

Adding Scroll Animations to the Scroll Label

Ok, now the last thing I want to do for this example is do something with the label that says "Scroll Down to See the List" as I scroll down. I think we should animate it out as we scroll since it’s telling us to scroll, and that’s what we’re doing.

So, you know the deal by now: let’s add the animation timeline with view and block.

.label {
    animation-timeline: view(block);
}

Now we need another keyframe animation, this one we’ll call fadeout. It will start from a scale of one and an opacity of one. It’ll animate to a vertical translation of negative two hundred and fifty percent, a scale of eighty percent, and an opacity of zero.

@keyframes fadeout {
    from {
        scale: 1;
        opacity: 1;
    }
    to {
        translate: 0 -250%;
        scale: 0.8;
        opacity: 0;
    }
}

Now, we need to add it to our label element.

.label {
    ...
    animation-name: fadeout;
}

Then, we need to add our range. This time, it’ll be a little different. We’ll start with contain at zero percent, and end with contain at one hundred percent.

.label {
    ...
    animation-range: contain 0% contain 100%;
}

Ok, so how does this look now?

https://codepen.io/brianmtreese/pen/MWLwdNw?embedable=true

Nice, I like that better.

Scroll-Driven Animations Browser Support

Hopefully, this gets you going and inspires you for what’s possible with the stuff that you’re building.

One last note on scroll-driven animations: browser support is not super great at the moment. Chrome and Edge are good, but they are only available behind a flag in Firefox and not at all in Safari.

Hopefully, we’ll get there soon, but in the meantime, you’ll need to keep that in mind.

Ok, I think that’s all for now. Until next time. Thanks for reading.


Also published here.


Written by briantreese | Hello I'm the Chief of UX at SoCreate. I build things for the web daily & write about the stuff I use/discover/encounter
Published by HackerNoon on 2024/02/16