frontend.fyi

CSS Scroll-Driven Animations — We Don't Need Any JS!

CSS scroll-driven animations enable us to create powerful scroll-based effects, achieved natively without external libraries.

Code on Github
🧑‍💻 🎥
... Almost ready!

If you’ve been following the other videos and articles, you know we’ve previously explored scroll animations mainly using Framer Motion. However, there’s a fascinating new feature in CSS that allows us to achieve similar effects natively, without relying on external libraries. Today, I’ll walk you through the process of creating captivating scroll-driven animations using this new CSS feature. Let’s get started!

Inspiration for this example

Just over a week ago, the tweet below caught my attention, showcasing a scroll-driven animation where text gradually shrinks as the user scrolls. Intrigued by the possibilities, I decided to experiment with CSS scroll-driven animations, which resulted in recreating this effect using a small CodeSandbox example. However, in this blog post, we’ll take it a step further and create a more elaborate example using Tailwind CSS.

Browser support

It’s good to know the feature is currently only available behind feature flags in certain browsers. (In Chrome you enabled it by opening chrome://flags and enabling the #enable-experimental-web-platform-features flag.) However, since almost all browsers have the feature in preview, that means we probably can expect it soon in all browsers.

Until then, we can use the @supports media query to provide graceful degradation for browsers that don’t support scroll-driven animations yet.

The Example: An Animated Hero Component

Our goal is to create two different types of scroll-driven animations. Firstly a hero that features text that moves down and fades out as the user scrolls, based on the scroll position of the window. Secondly, a big quote that gradually enlarges as the user continues scrolling, creating a parallax effect – doing so based on whether the element itself is in view or not.

Adding the hero scroll

Our first animation involves moving the text down and fading it out. We achieve this by defining a keyframe animation named fade-out-down. In case of Tailwind you add that to your Tailwind config like so:

1
keyframes: {
2
"fade-out-down": {
3
from: {
4
opacity: "1",
5
transform: "translateY(0)",
6
},
7
to: {
8
opacity: "0",
9
transform: "translateY(40%)",
10
},
11
},
12
},
13
animation: {
14
"fade-out-down": "fade-out-down linear forwards",
15
},

Connecting the animation to the scroll

After we’ve created the animation, we can add it to the H1 by adding the class animate-fade-out-down. That will however immediately run the animation, and hide the text.

In order to fix that, we have to add the animation timelines. In plain CSS you add this timeline by adding:

1
h1 {
2
animation-timeline:scroll();
3
}

Which in Tailwind we can add by using arbitrary values like so:

1
<h1 class="[animation-timeline:scroll()]">
2
Scroll-driven animations
3
</h1>

By default the scroll timeline will spread the animation over the full range of the page. Meaning the ‘100%’ will be completed once the user reaches the bottom of the page. In our case, that would mean we would never see the animation properly, because the hero would be out of view.

Controlling the animation range

Changing the range of the animation, can be done by using the new CSS property animation-range. By setting a specific range for the animation, we control when the animation starts and ends in relation to the scrolling. If you want the animation to only run for the first 300 pixels of the scroll, you’d add an animation-range like so:

1
h1 {
2
animation-range: 0 300px;
3
}

In Tailwind we can add this by using arbitrary values like so (note the underscore instead of the space):

1
<h1 class="[animation-range:0_300px]">
2
Scroll-driven animations
3
</h1>

Animating the quote element based on its own visibility

With scroll-driven animations you can do more than only attaching an animation to the page scroll. You can also attach an animation to the visibility of an element itself.

Instead of using the scroll timeline, we can use the view timeline. This view timeline is attached to a specific DOM element and looks as follows:

1
.quote-element {
2
animation-timeline: --quote;
3
view-timeline-name: --quote;
4
}

In Tailwind we can add this by using arbitrary values like so (not that we for now unfortunatley need to escape the -- part in Tailwind):

1
<div class="quote-element [animation-timeline:\-\-quote] [view-timeline-name:\-\-quote]">
2
...
3
</div>

Controlling the animation range

Just like with the scroll timeline, we can control the range of the animation. In this case, we can control the range of the animation based on the visibility of the element. By default, the animation will run for the full range of the element. However, we can change that by using the animation-range property, just like with scroll animations.

Check the MDN docs for more information about special properties like cover and entry.

Supporting older browsers

As mentioned earlier, scroll-driven animations are currently only available behind feature flags in certain browsers. However, we can use the @supports media query to provide graceful degradation for browsers that don’t support scroll-driven animations yet. This way, we can still provide a good experience for users on older browsers, and already start experimenting with it.

You can either decide to create a supports media query that checks whether the animation-timeline property is supported, or you can check if the browser does not support it. Especially in Tailwind checking for the negative is a bit easier, because that way you only need to override a single property.

1
@supports (animation-timeline: scroll()) {
2
.quote-element {
3
animation-timeline: --quote;
4
view-timeline-name: --quote;
5
}
6
}

In Tailwind you can create a custom supports classname in the config like so:

1
theme: {
2
supports: {
3
"scroll-timeline": "(animation-timeline: scroll())",
4
"no-scroll-timeline": "not (animation-timeline: scroll())",
5
},
6
},

Which you can then use like so: className="no-scroll-timeline:animate-none".

Conclusion

And there you have it! Through this detailed exploration, we’ve demonstrated how CSS scroll-driven animations can elevate your web design and storytelling capabilities. By leveraging the power of native CSS animations and the scroll timeline, you can create captivating and interactive user experiences without relying on external JavaScript libraries. As browser support for scroll-driven animations continues to expand, I’m excited about the creative possibilities it offers.