Ryan Trimble
Smoke swirled red and blue colors

CSS-Only Scroll Animations

Front End Development

Photo Credit: Ruvim Noga

During CSS Day, Bramus Van Damme did a fantastic two-subject talk on the View Transitions API and Scroll-Driven Animations. I wrote an article for Stephanie Eckles12 Days of Web on the View Transitions API over the holidays, and while a lot has changed with it since then, that will be a topic for another blog post. Instead, I want to talk about Scroll-Driven Animations today.

Scroll-Driven Animations usually are something you would need to reach towards JavaScript for, either using something like the Intersection Observer API or a library such as GreenSock Animation Platform to accomplish. However, a new feature is coming to the CSS specification, enabling scrolling as a timeline for CSS animations.

As of the time of writing, CSS Scroll-Driven Animations is an experimental feature that is only available in Chrome 116+ (Use Chrome Canary to test things out!).

New Properties

A couple of new CSS properties control Scroll-Driven Animations:

  • animation-timeline
  • animation-range

animation-timeline is used to specify what type of Scroll-Driven timeline will be used, such as a Scroll Progress Timeline or a View Progress Timeline.

Scroll Progress Timelines

The most basic use-case for animation-timeline is creating a Scroll Progress Timeline with the scroll() CSS function:

.animating-element {
  animation: fade-in ease;
  animation-timeline: scroll();
}

The scroll() function can also accept parameters for the scrolling element, or scroller, and the axis in which the scrolling will occur.

The scroller can be set as:

  • nearest - closest ancestor
  • root - the document root
  • self - the element itself

Or can be uniquely named by setting the scroll-timeline-name on the scrolling element.

The axis parameter specifies whether the scrolling will occur on the x or y axis, but can also be used with CSS logical properties such as inline or block.

.scroller{
  scroll-timeline-name: --scroller;
  scroll-timeline-axis: block;
}

.animating-element{
  animation: fade-in ease;
  animation-timeline: --scroller;
}

View Progress Timelines

A much more fun alternative to the Scroll Progress Timeline is creating a View Progress Timeline.

View Progress Timelines can start Scroll-Driven Animations when intersecting with certain parts of the scroller. You may think this is similar to Scroll-Triggered animations, but that is not the case - the animations are still scrubbed through by scrolling.

To create a View Progress Timeline, we can set the view() CSS function for animation-timeline. Similar to scroll(), the view() function can take in the axis of the scroller.

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

Where the magic happens is with another new CSS property, animation-range.

animation-range allows you to specify when the animation should start and stop based on when the element is intersecting different parts of the scroller.

The different parts of the scroller are labeled as such:

  • contain
  • cover
  • entry-crossing
  • entry
  • exit-crossing
  • exit

You must specify what percentage of each part as to when the animation should start and stop. The syntax would look like this:

.animating-element{
  animation: fade-in ease;
  animation-timeline: view(block);
  animation-range: contain 0 contain 100%;
}

I would recommend creating CSS Custom Properties to make the start and end points a bit clearer, such as:

.animating-element {
  --range-start: contain 0;
  --range-end: contain 100%;

  animation: fade-in ease;
  animation-timeline: view(block);
  animation-range: var(--range-start) var(--range-end);
}

Bramus has created an excellent tool to help understand this a bit better, I highly recommend checking it out.

Progressive Enhancement

The good news here is that CSS Scroll-Driven Animations are able to be implemented as a progressive enhancement for browsers that will support them.

This can be done with the good old, trusty @supports:

@supports (animation-timeline: scroll()){
  .animating-element {
    animation: fade-in ease;
    animation-timeline: scroll();
  }
}

This will check to see if the browser knows what animation-timeline: scroll() is and if so, apply the Scroll-Driven Animation.

Accessibility

Similar to progressive enhancement, Scroll-Driven Animations can also respect user’s motion preferences by being wrapped inside a prefers-reduced-motion media query:

@media(prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()){
    .animating-element {
      animation: fade-in ease;
      animation-timeline: scroll();
    }
  }
}

I personally like to use prefers-reduced-motion: no-preference as it is more akin to the user opting into animations, rather than the opposite.

Demonstration

For a more “real-life” example of Scroll-Driven Animations, check out this Codepen I have created:

I recommend checking that out fullscreen on Codepen.

Wrap-up

It’s absolutely amazing what CSS is capable of nowadays. It’s also great that we are needing less-and-less JavaScript dependencies when it comes to building out things like this. I’m super excited to see all the creative stuff that can be done with Scroll-Driven Animations.

For further reading on Scroll-Driven Animations, please check out these resources:

Let's work together!