Blog

CSS New features Scroll-driven Animations

Start using Scroll-driven animations today!

September 17, 2025 by Cyd Stumpel Reading time: 3 minutes

To celebrate scroll-driven animations finally landing in Safari 26, here are some things you probably want to know before using them.

Link to:

The anatomy of a scroll driven animation

.card {
  @media (prefers-reduced-motion: no-preference) {
    animation-timeline: view(); 
    /* or scroll(), optionally add the axis in which you're scrolling or leave empty for y */
    animation-name: rotate-card; /* your keyframe animation */
    animation-range: cover; 
    /* how long do you want your animation to run for? See sources for a visualizer */
    animation-fill-mode: both; 
    /* recommended to make sure animation doesn't return to initial state after being completed */
  }
}

@keyframes rotate-card {
  to {
    rotate: 10deg;
  }
}
CSS

We don’t need the animation-duration property as the animation duration is determined by the animation-range now. This code would rotate the element with the class .card 10 degrees over the range cover, which means: as long as the element is visible in the viewport it will animate.

Link to:

Animation ranges

So, animation ranges determine the length of the animation, the documentation on this is a difficult read, Bramus van Damme created this visualizer which helps a lot, but I also created a sketch with all the different ranges side by side.

You can combine the ranges, using for example animation-range: cover contain;, this means the animation will start at the cover start point and end at the contain end point.

But wait, we can make it more confusing! We can also add percentages! 🖖 Like: animation-range: cover 10% contain 90%;.

Let’s break this down; when you use animation-range: cover, that’s actually shorthand for animation-range: cover 0% cover 100%;. So cover 10% means that we’re starting at 10% of the cover start point, and contain 90% means we’re stopping at 90% of the contain range.

Still with me? Here’s where it get’s even worse… You can also use other CSS units in ranges! So what does animation-range: cover 10vh; do? Well it starts the range at the cover start range + 10vh of course! 😎

All kidding aside, it’s a new mental model for me, and I’m very disappointed salty that browser’s decided not to try and implement existing patterns from libs like GSAP for example, but once you get used to them a bit, they’re usable too 😅.

Link to:

View timelines vs. scroll timelines

View timelines (animation-timeline: view();) are determined by the element’s position within the viewport, scroll timelines (animation-timeline: scroll();) are determined by the scrollable container. I’m only focussing on view timelines in this article as I have barely used scroll timelines.

Link to:

View timeline name

You can set a different element to be used for the view-timeline-range, reference the element you want to use as a view timeline with view-timeline-name and in stead of using animation-timeline: view() you use the name you gave your view timeline.

.cards {
  view-timeline-name: --cards;
}

.card {
  @media (prefers-reduced-motion: no-preference) {
    animation-timeline: --cards; 
    /* or scroll(), optionally add the axis in which you're scrolling or leave empty for y */
    animation-name: rotate-card; /* your keyframe animation */
    animation-range: cover; 
    /* how long do you want your animation to run for? See sources for a visualizer */
    animation-fill-mode: both; 
    /* recommended to make sure animation doesn't return to initial state after being completed */
  }
}

@keyframes rotate-card {
  to {
    rotate: 10deg;
  }
}
CSS

Now in stead of the cards’ visibility determining the range it will be it’s outer container: .cards. The element you reference using view-timeline-name needs to either be a direct parent of the element you’re animating or you need to add the name in timeline-scope on a shared parent. Read more about using timeline-scope here.

Link to:

A simple example

See the Pen Simple scroll driven animations by Cyd Stumpel (@Sidstumple) on CodePen.

In this simple example I’ve added the code from the very start of this article, the range is set to cover so as long as the card is visible within the viewport the element is animating.

If I use the .cards section as a view-timeline reference the cards will animate on the same range; as long as the section is in view.

See the Pen Simple scroll driven animations view-timeline-name by Cyd Stumpel (@Sidstumple) on CodePen.

Link to:

Accessibility

When you’re adding animation to your website you always need to keep in mind that some people get sick from motion, and respect a user’s preferences. You can encase your code in a prefers-reduced-motion media query that’s set to no-preference.


@media (prefers-reduced-motion: no-preference) {
  /* Your animations */
}
CSS
Link to:

Fallbacks

A lot of people don’t immediately update their browsers or operating systems, and scroll driven animations aren’t supported in Firefox yet, so it’s smart to create fallbacks, I wrote an earlier article about two strategies to do just that!

Link to:

More examples

I have created a few more examples using Scroll-driven Animations on codepen, saved in this collection.

Link to:

Sources

Cyd Stumpel

Cyd is a Freelance Creative Developer and teacher from Amsterdam. She teaches at the Amsterdam University of Applied Sciences and occastionally speaks at conferences and meetups.

Last updated: January 3, 2026

85 Webmentions

Join the conversation on Bluesky Bluesky Mastodon Mastodon

Replies 4

Una Kravets Bluesky Bluesky

Una Kravets@una.im

5 months ago on Bluesky

See @cydstumpel.nl‘s other post on progressively enhancing with scroll-driven animations, too!

cydstumpel.nl/two-approach…

claas Bluesky Bluesky

claas@claas.dev

5 months ago on Bluesky

The title is stuck in the middle of the page on Firefox mobile (Android)

Cyd Stumpel Bluesky Bluesky

Cyd Stumpel@cydstumpel.nl

5 months ago on Bluesky

Damn, that’s rough haha, pushing a fix now, thanks for letting me know

Keenan Payne Bluesky Bluesky

Keenan Payne@keenanpayne.bsky.social

5 months ago on Bluesky

Thank you for the great write-up

Reposts 19

Jon Henshaw Mastodon Mastodon

Jon Henshaw

View source
Landing.Love Bluesky Bluesky

Landing.Love

View source
@joellesenne.dev Bluesky Bluesky

@joellesenne.dev

View source
Marc Thiele Mastodon Mastodon

Marc Thiele

View source
Brian Bluesky Bluesky

Brian

View source
ML | Merkyl Bluesky Bluesky

ML | Merkyl

View source
Martin Laxenaire Bluesky Bluesky

Martin Laxenaire

View source
Sean van Zuidam Bluesky Bluesky

Sean van Zuidam

View source
Adam Argyle Bluesky Bluesky

Adam Argyle

View source
Hidden Thoughts Bluesky Bluesky

Hidden Thoughts

View source
Vasilis Mastodon Mastodon

Vasilis

View source
Ana Tudor Mastodon Mastodon

Ana Tudor

View source
Bramus Mastodon Mastodon

Bramus

View source
Bramus Bluesky Bluesky

Bramus

View source
Una Kravets Bluesky Bluesky

Una Kravets

View source
Bluesky Bluesky

@oclan.bsky.social

View source
Andy Bell Bluesky Bluesky

Andy Bell

View source
Nilesh Prajapati Bluesky Bluesky

Nilesh Prajapati

View source
Christian Bluesky Bluesky

Christian “Schepp” Schaefer

View source

Webmentions are a way to connect with other people who have shared your work.