CSS Scroll Animations

  • Animation
  • CSS
  • Web Design & Development

Scroll-driven animations hit a major milestone recently, moving out behind browser flags to become released in Chrome and Edge versions 115. With that, we now have the ability to make some really powerful and performant animations all inside CSS (as well as JavaScript).

So note, if you want to try out any of these demos without messing with flags, make sure you use the latest Chrome or Edge.

”Scroll triggered” versus “scroll driven”

Typically, when we think of scrolling being responsible for animation, we’d probably think of one of two scenarios:

  1. As the user scrolls, when a certain threshold is crossed (usually an element coming into the viewport) and an animation is triggered. The animation plays in its entirety when this event happens, regardless of whether the user continues to scroll.

  2. As the user scrolls, an animation is “scrubbed”. The scrolling action is directly tied to how advanced the animation is.

These scenarios tend to be referred to as “scroll triggered” and “scroll driven”, respectively. The new CSS are geared towards “scroll driven”, since scrolling directly controls the progress of the animation. However, we can make some animations very close to their “scroll triggered” counterparts (more on that in a few paragraphs). In many of those instances, an API like Intersection Observer would still do the trick.

animation-timeline

Meet our new CSS property that helps make the magic happen: animation-timeline. This is a little technical, but all animations on the page are usually attributed to the document timeline. The document timeline is the amount of time that has passed since the page loaded. The new API gives us access to two additional timelines: scroll timeline and view timeline.

Scroll timeline

For the scrolling timeline, the progress of the scroll is converted to the progress through an animation. This can be the nearest parent with scroll, but it can be a scrolling area specified. You can also specify the axis that you’re working on. Something like the following:

.element {
  animation-timeline: scroll(root block);
}

The above is an example of an anonymous timeline, using the scroll() function. For a named timeline, you could use the aptly named scroll-timeline-name property on the scrolling element you’re interested in, and then use that value with the animating element’s animation-timeline. Here’s a visualizer tool to help you see how an animation and scroll position are linked. Some of you may note that this could closely resemble implementations of “reading progress”, but without all the extra JavaScript.

View timeline

View timeline is based on element visibility inside a scrollable area (or “scroller”). By default, when an element is first visible at one edge, the animation is at 0%. When it reaches the opposite edge, the animation progresses to 100% (though these settings can be changed as well). Like the other timeline, you can use anonymous or named timelines via view() and view-timeline-name. An anonymous implementation might look something like:

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

When combined with another new property, animation-range, this can be particularly powerful, and get very close to “scroll triggered” animations mentioned earlier. This is all a lot to wrap your mind around, so this interactive playground may help, as well as this simplified demo, and this “Fast and Furious” demo.

If you want to know more on the technical aspects as well as some additional examples, MDN has this wonderful blog post by Michelle Barker that can help explain things as well.

But why?

According to this article by Yuriko Hirota on Google’s Chrome Developers blog:

In the past, the only way to create scroll-driven animations was to respond to the scroll event on the main thread. This caused two major problems:

  • Scrolling is performed on a separate thread and therefore delivers scroll events asynchronously.

  • Main thread animations are subject to jank.

…The API tries to use as few main thread resources as possible, making scroll-driven animations far easier to implement, and also much smoother.

Yuriko does a deep dive on the performance side in their article, comparing classic JavaScript techniques to the new API in both CSS and JavaScript (yes, we also got this as part of the Web Animations API). This provides some great insight into why performant animations are so important to a great user experience, and how easily some can be derailed by heavy main thread work.

Enough! Show us some demos!

Chrometober

This first one is Chrometober 2022. Scroll horizontally to “read” the book! Then, read up on how it was built.

”Cover flow”

All of you out there with fancy Apple devices have probably seen their “cover flow” layout at one point or another. Now, this type of animation can be done entirely with CSS. Check out the cover flow demo.

Apple style text reveal

Some Apple inspired scrolling as well!

This is a great example of how scroll driven animation could lead to a more immersive experience as the user scrolls down the page. Particularly because it builds off of some design ideas quite a few of us have already experienced.

Stacking cards

Another from the same site as the “cover flow”, this stacking cards animation shows how versatile these animations can get. These cards animate into frame and “stick” to the top, creating layers as you get more cards.

Horizontal scrolling with transform

Jhey is back again (from the previous pen). This time, using inspiration from a site that posted their element on Awwwards. Here is a video of the original:

Normally, this type of animation might be pretty intense on performance and potentially difficult to get just right going horizontally. Not with our new CSS superpowers. Check out Jhey’s version.

”Tokyo Scroll”

Jhey pointed me to this one as well, which does a great job of visualizing some of the settings specifically for animation-range. Fun fact, even the numbered percentage on the progress bars is done with pure CSS 🤯.

Scroll animations FTW

This is just the tip of the iceberg, but I hope it has you excited for when these finally get full support in all modern browsers. Just this small step has me really excited for what we could do in the future. There are even more updates incoming, like timeline-scope (still behind flags for another version at least).

If you need more inspiration, Jhey has a bunch more demos on his Codepen (and you should just follow him anyways). Big thanks to Jhey for letting me name drop him so much on this post, proofing, and using his work as examples.

I’d also like to mention with the above articles, there are a couple good YouTube videos on this by Google Chrome Developers as well. Check them out!

Happy scrolling!