CSS-Only Scroll Animations
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 Eckles’ 12 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 ancestorroot
- the document rootself
- 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: