You may safely use scroll-driven animations in Chrome as of December 2024. Firefox helps them, too, although you’ll must allow a flag. Safari? Not but, however don’t fear — you possibly can nonetheless supply a seamless expertise throughout all browsers with a polyfill. Simply remember that including a polyfill entails a JavaScript library, so that you gained’t get the identical efficiency enhance.
There are many precious sources to dive into scroll-driven animations, which I’ll be linking all through the article. My place to begin was Bramus’ video tutorial, which pairs properly with Geoff’s in-depth notes Graham that construct on the tutorial.
On this article, we’ll stroll by means of the newest revealed model by the W3C and discover the 2 sorts of scroll-driven timelines — scroll progress timelines and view progress timelines. By the top, I hope that you’re accustomed to each timelines, not solely with the ability to inform them aside but in addition feeling assured sufficient to make use of them in your work.
Observe: All demos on this article solely work in Chrome 116 or later on the time of writing.
The scroll progress timeline hyperlinks an animation’s timeline to the scroll place of a scroll container alongside a particular axis. So, the animation is tied on to scrolling. As you scroll ahead, so does the animation. You’ll see me confer with them as scroll-timeline
animations along with calling them scroll progress timelines.
Simply as we have now two sorts of scroll-driven animations, we have now two sorts of scroll-timeline
animations: nameless timelines and named timelines.
Nameless scroll-timeline
Let’s begin with a basic instance: making a scroll progress bar on the high of a weblog submit to trace your studying progress.
On this instance, there’s a <div>
with the ID “progress.” On the finish of the CSS file, you’ll see it has a background shade, an outlined width and peak, and it’s fastened on the high of the web page. There’s additionally an animation that scales it from 0
to 1
alongside the x-axis — fairly customary for those who’re accustomed to CSS animations!
Right here’s the related a part of the types:
#progress {
/* ... */
animation: progressBar 1s linear;
}
@keyframes progressBar {
from { rework: scaleX(0); }
}
The progressBar
animation runs as soon as and lasts one second with a linear timing perform. Linking this animation scrolling is only a single line in CSS:
animation-timeline: scroll();
No must specify seconds for the period — the scrolling habits itself will dictate the timing. And that’s it! You’ve simply created your first scroll-driven animation! Discover how the animation’s path is straight tied to the scrolling path — scroll down, and the progress indicator grows wider; scroll up, and it turns into narrower.
scroll-timeline
Property Parameters
In a scroll-timeline
animation, the scroll()
perform is used contained in the animation-timeline
property. It solely takes two parameters: <scroller>
and <axis>
.
<scroller>
refers back to the scroll container, which may be set asnearest
(the default),root
, orself
.<axis>
refers back to the scroll axis, which may beblock
(the default),inline
,x
, ory
.
Within the studying progress instance above, we didn’t declare any of those as a result of we used the defaults. However we might obtain the identical outcome with:
animation-timeline: scroll(nearest block);
Right here, the nearest
scroll container is the foundation scroll of the HTML component. So, we might additionally write it this manner as an alternative:
animation-timeline: scroll(root block);
The block
axis confirms that the scroll strikes high to backside in a left-to-right writing mode. If the web page has a large horizontal scroll, and we wish to animate alongside that axis, we might use the inline
or x
values (relying on whether or not we wish the scrolling path to at all times be left-to-right or adapt based mostly on the writing mode).
We’ll dive into self
and inline
in additional examples later, however the easiest way to be taught is to mess around with all of the mixtures, and this instrument by Bramus helps you to do precisely that. Spend a couple of minutes earlier than we leap into the subsequent property related to scroll timelines.
The animation-range
Property
The animation-range
for scroll-timeline
defines which a part of the scrollable content material controls the beginning and finish of an animation’s progress based mostly on the scroll place. It lets you resolve when the animation begins or ends whereas scrolling by means of the container.
By default, the animation-range
is about to regular
, which is shorthand for the next:
animation-range-start: regular;
animation-range-end: regular;
This interprets to 0%
(begin
) and 100%
(finish
) in a scroll-timeline
animation:
animation-range: regular regular;
…which is identical as:
animation-range: 0% 100%;
You may declare any CSS size models and even calculations. For instance, let’s say I’ve a footer that’s 500px
tall. It’s full of banners, advertisements, and associated posts. I don’t need the scroll progress bar to incorporate any of that as a part of the studying progress. What I would like is for the animation to begin on the high and finish 500px
earlier than the underside. Right here we go:
animation-range: 0% calc(100% - 500px);
Similar to that, we’ve lined the important thing properties of scroll-timeline
animations. Able to take it a step additional?
Named scroll-timeline
Let’s say I wish to use the scroll place of a unique scroll container for a similar animation. The scroll-timeline-name
property lets you specify which scroll container the scroll animation needs to be linked to. You give it a reputation (a dashed-ident, e.g., --my-scroll-timeline
) that maps to the scroll container you wish to use. This container will then management the animation’s progress because the person scrolls by means of it.
Subsequent, we have to outline the scroll axis for this new container through the use of the scroll-timeline-axis
, which tells the animation which axis will set off the movement. Right here’s the way it seems within the code:
.my-class {
/* That is my new scroll-container */
scroll-timeline-name: --my-custom-name;
scroll-timeline-axis: inline;
}
In case you omit the axis, then the default block
worth can be used. Nevertheless, you may also use the shorthand scroll-timeline
property to mix each the title and axis in a single declaration:
.my-class {
/* Shorthand for scroll-container with axis */
scroll-timeline: --my-custom-name inline;
}
I feel it’s simpler to know all this with a sensible instance. Right here’s the identical progress indicator we’ve been working with, however with inline scrolling (i.e., alongside the x-axis):
Now we have two animations working:
- A progress bar grows wider when scrolling in an inline path.
- The container’s background shade modifications the additional you scroll.
The HTML construction seems like the next:
<div class="gallery">
<div class="gallery-scroll-container">
<div class="gallery-progress" position="progressbar" aria-label="progress"></div>
<img src="https://smashingmagazine.com/2024/12/introduction-css-scroll-driven-animations/image1.svg" alt="Alt textual content" draggable="false" width="500">
<img src="image2.svg" alt="Alt textual content" draggable="false" width="500">
<img src="image3.svg" alt="Alt textual content" draggable="false" width="500">
</div>
</div>
On this case, the gallery-scroll-container
has horizontal scrolling and modifications its background shade as you scroll. Usually, we might simply use animation-timeline: scroll(self inline)
to attain this. Nevertheless, we additionally need the gallery-progress
component to make use of the identical scroll for its animation.
The gallery-progress
component is the primary inside gallery-scroll-container
, and we’ll lose it when scrolling until it’s completely positioned. However after we do that, the component now not occupies house within the regular doc move, and that impacts how the component behaves with its dad or mum and siblings. We have to specify which scroll container we wish it to hearken to.
That’s the place naming the scroll container is useful. By giving gallery-scroll-container
a scroll-timeline-name
and scroll-timeline-axis
, we will guarantee each animations sync to the identical scroll:
.gallery-scroll-container {
/* ... */
animation: bg steps(1);
scroll-timeline: --scroller inline;
}
And is utilizing that scrolling to outline its personal animation-timeline
:
.gallery-scroll-container {
/* ... */
animation: bg steps(1);
scroll-timeline: --scroller inline;
animation-timeline: --scroller;
}
Now we will scale this title to the progress bar that’s utilizing a unique animation however listening to the identical scroll:
.gallery-progress {
/* ... */
animation: progressBar linear;
animation-timeline: --scroller;
}
This enables each animations (the rising progress bar and altering background shade) to comply with the identical scroll habits, regardless that they’re separate components and animations.
The timeline-scope
Property
What occurs if we wish to animate one thing based mostly on the scroll place of a sibling or perhaps a greater ancestor? That is the place the timeline-scope
property comes into play. It permits us to increase the scope of a scroll-timeline
past the present component’s subtree. The worth of timeline-scope
have to be a {custom} identifier, which once more is a dashed-ident.
Let’s illustrate this with a brand new instance. This time, scrolling in a single container runs an animation inside one other container:
We will play the animation on the picture when scrolling the textual content container as a result of they’re siblings within the HTML construction:
<div class="main-container">
<div class="sardinas-container">
<img ...>
</div>
<div class="scroll-container">
<p>Lengthy textual content...</p>
</div>
</div>
Right here, solely the .scroll-container
has scrollable content material, so let’s begin by naming this:
.scroll-container {
/* ... */
overflow-y: scroll;
scroll-timeline: --containerText;
}
Discover that I haven’t specified the scroll axis, because it defaults to block
(vertical scrolling), and that’s the worth I would like.
Let’s transfer on to the picture contained in the sardinas-container
. We wish this picture to animate as we scroll by means of the scroll-container
. I’ve added a scroll-timeline-name
to its animation-timeline
property:
.sardinas-container img {
/* ... */
animation: moveUp steps(6) each;
animation-timeline: --containerText;
}
At this level, nonetheless, the animation nonetheless gained’t work as a result of the scroll-container
will not be straight associated to the photographs. To make this work, we have to prolong the scroll-timeline-name
so it turns into reachable. That is performed by including the timeline-scope
to the dad or mum component (or the next ancestor) shared by each components:
.main-container {
/* ... */
timeline-scope: --containerText;
}
With this setup, the scroll of the scroll-container
will now management the animation of the picture contained in the sardinas-container
!
Now that we’ve lined find out how to use timeline-scope
, we’re prepared to maneuver on to the subsequent sort of scroll-driven animations, the place the identical properties will apply however with slight variations in how they behave.
View Progress Timelines
We simply checked out scroll progress animations. That’s the primary sort of scroll-driven animation of the 2. Subsequent, we’re turning our consideration to view progress animations. There’s numerous similarities between the 2! However they’re completely different sufficient to warrant their very own part for us to discover how they work. You’ll see me refer to those as view-timeline
animations along with calling them view progress animations, as they revolve round a view()
perform.
The view progress timeline is the second sort of sort of scroll-driven animation that we’re . It tracks a component because it enters or exits the scrollport (the seen space of the scrollable content material). This habits is kind of just like how an IntersectionObserver
works in JavaScript however may be performed totally in CSS.
Now we have nameless and named view progress timelines, simply as we have now nameless and named scroll progress animations. Let’s unpack these.
Nameless View Timeline
Right here’s a easy instance to assist us see the essential thought of nameless view timelines. Discover how the picture fades into view whenever you scroll all the way down to a sure level on the web page:
Let’s say we wish to animate a picture that fades in because it seems within the scrollport. The picture’s opacity will go from 0
to 1
. That is the way you would possibly write that very same animation in basic CSS utilizing @keyframes
:
img {
/* ... */
animation: fadeIn 1s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
That’s nice, however we wish the picture to fadeIn
when it’s in view. In any other case, the animation is kind of like a tree that falls in a forest with nobody there to witness it… did the animation ever occur? We’ll by no means know!
Now we have a view()
perform that makes this a view progress animation with a single line of CSS:
img {
/* ... */
animation: fadeIn;
animation-timeline: view();
}
And see how we now not must declare an animation-duration
like we did in basic CSS. The animation is now not tied by time however by house. The animation is triggered because the picture turns into seen within the scrollport.
View Timeline Parameters
Similar to the scroll-timeline
property, the view-timeline
property accepts parameters that enable for extra customization:
animation-timeline: view( );
<inset>
Controls when the animation begins and ends relative to the component’s visibility inside the scrollport. It defines the margin between the sides of the scrollport and the component being tracked. The default worth isauto
, however it could actually additionally take size percentages in addition to begin and finish values.<axis>
That is just like the scroll-timeline’s axis parameter. It defines which axis (horizontal or vertical) the animation is tied to. The default isblock
, which implies it tracks the vertical motion. It’s also possible to useinline
to trace horizontal motion or easyx
ory
.
Right here’s an instance that makes use of each inset
and axis
to customise when and the way the animation begins:
img {
animation-timeline: view(20% block);
}
On this case:
- The animation begins when the picture is 20% seen within the scrollport.
- The animation is triggered by vertical scrolling (
block
axis).
Parallax Impact
With the view()
perform, it’s additionally straightforward to create parallax results by merely adjusting the animation properties. For instance, you possibly can have a component transfer or scale because it enters the scrollport with none JavaScript:
img {
animation: parallaxMove 1s;
animation-timeline: view();
}
@keyframes parallaxMove {
to { rework: translateY(-50px); }
}
This makes it extremely easy to create dynamic and fascinating scroll animations with just some traces of CSS.
The animation-range
Property
Utilizing the CSS animation-range
property with view timelines defines how a lot of a component’s visibility inside the scrollport controls the beginning and finish factors of the animation’s progress. This can be utilized to fine-tune when the animation begins and ends based mostly on the component’s visibility within the viewport.
Whereas the default worth is regular
, in view timelines, it interprets to monitoring the total visibility of the component from the second it begins coming into the scrollport till it totally leaves. That is represented by the next:
animation-range: regular regular;
/* Equal to */
animation-range: cowl 0% cowl 100%;
Or, extra merely:
animation-range: cowl;
There are six potential values or timeline-range-names
:
cowl
Tracks the total visibility of the component, from when it begins coming into the scrollport to when it fully leaves it.include
Tracks when the component is totally seen contained in the scrollport, from the second it’s totally contained till it now not is.entry
Tracks the component from the purpose it begins coming into the scrollport till it’s totally inside.exit
Tracks the component from the purpose it begins, leaving the scrollport till it’s totally exterior.entry-crossing
Tracks the component because it crosses the beginning fringe of the scrollport, from begin to full crossing.exit-crossing
Tracks the component because it crosses the top fringe of the scrollport, from begin to full crossing.
You may combine completely different timeline-range-names
to regulate the beginning and finish factors of the animation vary. For instance, you may make the animation begin when the component enters the scrollport and finish when it exits:
animation-range: entry exit;
It’s also possible to mix these values with percentages to outline extra {custom} habits, similar to beginning the animation midway by means of the component’s entry and ending it midway by means of its exit:
animation-range: entry 50% exit 50%;
Exploring all these values and mixtures is finest performed interactively. Instruments like Bramus’ view-timeline vary visualizer make it simpler to know.
Goal Vary Inside @keyframes
One of many highly effective options of timeline-range-names
is their capacity for use inside @keyframes
:
Two completely different animations are occurring in that demo:
slideIn
When the component enters the scrollport, it scales up and turns into seen.slideOut
When the component leaves, it scales down and fades out.
@keyframes slideIn {
from {
rework: scale(.8) translateY(100px);
opacity: 0;
}
to {
rework: scale(1) translateY(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
rework: scale(1) translateY(0);
opacity: 1;
}
to {
rework: scale(.8) translateY(-100px);
opacity: 0
}
}
The brand new factor is that now we will merge these two animations utilizing the entry
and exit
timeline-range-names
, simplifying it into one animation that handles each circumstances:
@keyframes slideInOut {
/* Animation for when the component enters the scrollport */
entry 0% {
rework: scale(.8) translateY(100px);
opacity: 0;
}
entry 100% {
rework: scale(1) translateY(0);
opacity: 1;
}
/* Animation for when the component exits the scrollport */
exit 0% {
rework: scale(1) translateY(0);
opacity: 1;
}
exit 100% {
rework: scale(.8) translateY(-100px);
opacity: 0;
}
}
entry 0%
Defines the state of the component initially of its entry into the scrollport (scaled down and clear).entry 100%
Defines the state when the component has totally entered the scrollport (totally seen and scaled up).exit 0%
Begins monitoring the component because it begins to depart the scrollport (seen and scaled up).exit 100%
Defines the state when the component has totally left the scrollport (scaled down and clear).
This strategy permits us to animate the component’s habits easily because it each enters and leaves the scrollport, all inside a single @keyframes
block.
Named view-timeline
And timeline-scope
The idea of utilizing view-timeline
with named timelines and linking them throughout completely different components can actually develop the probabilities for scroll-driven animations. On this case, we’re linking the scroll-driven animation of photographs with the animations of unrelated paragraphs within the DOM construction through the use of a named view-timeline
and timeline-scope
.
The view-timeline
property works equally to the scroll-timeline
property. It’s the shorthand for declaring the view-timeline-name
and view-timeline-axis
properties in a single line. Nevertheless, the distinction from scroll-timeline
is that we will hyperlink the animation of a component when the linked components enter the scrollport. I took the earlier demo and added an animation to the paragraphs so you possibly can see how the opacity of the textual content is animated when scrolling the photographs on the left:
This one seems a bit verbose, however I discovered it arduous to give you a greater instance to point out the ability of it. Every picture within the vertical scroll container is assigned a named view-timeline
with a singular identifier:
.vertical-scroll-container img:nth-of-type(1) { view-timeline: --one; }
.vertical-scroll-container img:nth-of-type(2) { view-timeline: --two; }
.vertical-scroll-container img:nth-of-type(3) { view-timeline: --three; }
.vertical-scroll-container img:nth-of-type(4) { view-timeline: --four; }
This makes the scroll timeline of every picture have its personal {custom} title, similar to --one
for the primary picture, --two
for the second, and so forth.
Subsequent, we join the animations of the paragraphs to the named timelines of the photographs. The corresponding paragraph ought to animate when the photographs enter the scrollport:
.vertical-text p:nth-of-type(1) { animation-timeline: --one; }
.vertical-text p:nth-of-type(2) { animation-timeline: --two; }
.vertical-text p:nth-of-type(3) { animation-timeline: --three; }
.vertical-text p:nth-of-type(4) { animation-timeline: --four; }
Nevertheless, because the photographs and paragraphs aren’t straight associated within the DOM, we have to declare a timeline-scope
on their frequent ancestor. This ensures that the named timelines (--one
, --two
, and so forth) may be referenced and shared between the weather:
.porto {
/* ... */
timeline-scope: --one, --two, --three, --four;
}
By declaring the timeline-scope
with all of the named timelines (--one
, —two
, --three
, --four
), each the photographs and the paragraphs can take part in the identical scroll-timeline logic, regardless of being in separate components of the DOM tree.
Last Notes
We’ve lined the overwhelming majority of what’s at present outlined within the CSS Scroll-Pushed Animations Module Leve 1 specification at present in December 2024. However I wish to spotlight a couple of key takeaways that helped me higher perceive these new guidelines that you could be not get straight from the spec:
- Scroll container necessities
It might appear apparent, however a scroll container is important for scroll-driven animations to work. Points usually come up when components like textual content or containers are resized or when animations are examined on bigger screens, inflicting the scrollable space to vanish. - Impression of
place: absolute
Utilizing absolute positioning can generally intrude with the meant habits of scroll-driven animations. The connection between components and their dad or mum components will get difficult whenplace: absolute
is utilized. - Monitoring a component’s preliminary state
The browser evaluates the component’s state earlier than any transformations (liketranslate
) are utilized. This impacts when animations, notably view timelines, start. Your animation would possibly set off earlier or later than anticipated because of the preliminary state. - Keep away from hiding overflow
Utilizingoverflow: hidden
can disrupt the scroll-seeking mechanism in scroll-driven animations. The beneficial resolution is to modify tooverflow: clip
. Bramus has an ideal article about this and a video from Kevin Powell additionally means that we could now not wantoverflow: hidden
. - Efficiency
For the most effective outcomes, follow animating GPU-friendly properties like transforms, opacity, and a few filters. These skip the heavy lifting of recalculating format and repainting. However, animating issues likewidth
,peak
, orbox-shadow
can sluggish issues down since they require re-rendering. Bramus talked about that quickly, extra properties — likebackground-color
,clip-path
,width
, andpeak
— can be animatable on the compositor, making the efficiency even higher. - Use
will-change
properly
Leverage this property to advertise components to the GPU, however use it sparingly. Overusingwill-change
can result in extreme reminiscence utilization because the browser reserves sources even when the animations don’t often change. - The order issues
In case you are utilizing theanimation
shorthand, at all times place theanimation-timeline
after it. - Progressive enhancement and accessibility
Mix media queries for decreased movement preferences with the@helps
rule to make sure animations solely apply when the person has no movement restrictions, and the browser helps them.
For instance:
@media display screen and (prefers-reduce-motion: no-preference) {
@helps ((animation-timeline: scroll()) and (animation-range: 0% 100%)) {
.my-class {
animation: moveCard linear each;
animation-timeline: view();
}
}
}
My most important wrestle whereas attempting to construct the demos was extra about CSS itself than the scroll animations. Generally, constructing the format and producing the scroll was harder than making use of the scroll animation. Additionally, some issues that confused me initially because the spec retains evolving, and a few of these aren’t there anymore (keep in mind, it has been beneath improvement for greater than 5 years now!):
- x and y axes
These was once referred to as the “horizontal” and “vertical” axes, and whereas Firefox should still help the outdated terminology, it has been up to date. - Previous
@scroll-timeline
syntax
Prior to now,@scroll-timeline
was used to declare scroll timelines, however this has modified in the latest model of the spec. - Scroll-driven vs. scroll-linked animations
Scroll-pushed animations had been initially referred to as scroll-linked animations. In case you come throughout this older time period in articles, double-check whether or not the content material has been up to date to mirror the newest spec, notably with options liketimeline-scope
.
Assets
(gg, yk)