As of December 2024, you can use scroll-driven animated in Chrome. Firefox also supports them, but you’ll have to enable a flag. Safari? You can still provide a seamless experience in all browsers by using a polyfill. You won’t see the same performance boost if you add a polyfill. It requires a JavaScript Library. I’ll link to many resources that will help you dive into scroll-driven animated. I started with Bramus’ video, which pairs well with Geoff’s detailed notes Graham that build upon the tutorial. In this article we will walk through the most recent version published by the W3C, and explore the two scroll-driven time lines — scroll progress and view progress. By the end of this article, I hope you will be familiar with both timelines and feel confident enough to use either one in your work. As of the writing of this article, all demos only work with Chrome 116 and later. Scroll Progress Timelines A scroll progress timeline relates an animation’s time line to the scroll position along a particular axis. The animation is directly tied to scrolling. The animation moves forward as you scroll. You’ll hear me refer to them both as scroll-timeline timelines and scroll progress timelines. We have two types scroll-driven animations: anonymous and named timelines. Anonymous scroll-timeline First, let’s look at a classic example. Create a scroll bar at the top a blog to track your progress in reading. See the Pen Scroll Progress Timeline Example – Before animation-timeline Scroll() [forked] Mariana Beldi. This example has a with ID “progress” at the end of the CSS. It has a background colour, a defined height and width, and is fixed to the top of the webpage. The animation scales the image from 0 to 1, along the x axis. This is pretty standard for CSS animations. Here’s what you need to know: #progress /*…*/ animation: ProgressBar 1s linear; @keyframes: progressBar; from: transform: scaleX(0). This animation scrolling can be linked with just one line of CSS: animation-timeline scroll(). There is no need to specify the duration in seconds, as the scrolling behavior will determine the timing. That’s all! You have just created your very first scroll-driven animated video! The direction of the animation is directly related to the direction of scrolling — scroll down and the progress indicator becomes wider; scroll upwards and it becomes smaller. See the Pen Scroll Progress Timeline Example – animation timeline scroll() by Mariana Beldi. scroll-timeline Parameters When creating a scroll-timeline, the scroll() method is used within the animation-timeline parameter. It only accepts two parameters:. Scroll container can be set to nearest (the default), self, root, or root. Refers to the scroll axis. It can be set as block (the default), Inline, x or y. In the example of the reading progress, we did not declare any of these as we used the defaults. We could achieve the same results with: animation-timeline : scroll(nearest blocks); Here, root scroll is the nearest scroll container. We could also write this: animation-timeline : scroll(rootblock); The block axis confirms the scroll moves from top to bottom when writing in left-to-right mode. If the page has an extensive horizontal scroll and we wish to animate the axis along that scroll, we can use either the inline or the x values. (Depending on whether the scrolling direction should always be left to right or if it should adapt depending on the writing mode). We’ll explore self and inline with more examples, but for now, you can play around and experiment with all the different combinations using this Bramus tool. Spend a few moments before we move on to the next property related to scroll timelines. The animation range property The animation range for scroll-timeline specifies which part of scrollable content controls when an animation progresses based on scroll position. You can decide when an animation begins or ends as you scroll through the container. By default, the animation-range is set to normal, which is shorthand for the following: animation-range-start: normal; animation-range-end: normal; This translates to 0% (start) and 100% (end) in a scroll-timeline animation: animation-range: normal normal; …which is the same as: animation-range: 0% 100%; You can declare any CSS length units or even calculations. Let’s say, for example, that I have a 500px-tall footer. It’s full of banners, ads and related posts. I don’t wish to have any of those items included in the scroll progress bar. What I want is that the animation start at the top of the page and end 500px above the bottom. Here’s what we do: animation-range 0% calc (100% – 500px); See Pen Scroll Progress Timeline Example – Animation-Timeline, Animation-Range [forked] Mariana Beldi. We’ve now covered the key properties for scroll-timeline animated effects. Are you ready to go one step further? Named scroll-timeline Suppose I want to use a different scroll container’s scroll position for the same animation. Scroll-timeline-name allows you to specify the scroll container to which the scroll animation is linked. You give it a name (a dashed-ident, e.g., –my-scroll-timeline) that maps to the scroll container you want to use. This container will control the animation progress as the user scrolls. Next, we need to define the scroll axis for this new container by using the scroll-timeline-axis, which tells the animation which axis will trigger the motion. Here’s how it looks in the code: .my-class /* This is my new scroll-container */ scroll-timeline-name: –my-custom-name; scroll-timeline-axis: inline; If you omit the axis, then the default block value will be used. You can also combine the axis and the name in a single declaration by using the scroll-timeline shorthand property:.my.class /* A shortcut for scroll-containers with axis. */ Scroll-timeline: my-custom-name, inline. Here’s the progress indicator we have been working with but with inline (i.e. along the x axis) scrolling: See the Pen Named Scroll Progress Timeline by Mariana Beldi. Two animations are running: A progress bar gets wider when scrolling along the x-axis. The background color of the container changes as you scroll. The HTML structure looks like the following: In this case, the gallery-scroll-container has horizontal scrolling and changes its background color as you scroll. To achieve this, we would normally use animation-timeline scroll(self inline). We also want to use the same animation for the gallery-progress. The gallery-progress element is the first inside gallery-scroll-container, and we will lose it when scrolling unless it’s absolutely positioned. When we do this, however, the element will no longer occupy space in the normal document flow. This will affect how the element behaves when interacting with its siblings and parent. We must specify the scroll container to which we want it to listen. This is where naming the container comes in handy. By giving gallery-scroll-container a scroll-timeline-name and scroll-timeline-axis, we can ensure both animations sync to the same scroll: .gallery-scroll-container /* … */ animation: bg steps(1); scroll-timeline: –scroller inline; And is using that scrolling to define its own animation-timeline: .gallery-scroll-container /* … */ animation: bg steps(1); scroll-timeline: –scroller inline; animation-timeline: –scroller; Now we can scale this name to the progress bar that is using a different animation but listening to the same scroll: .gallery-progress /* … */ animation: progressBar linear; animation-timeline: –scroller; This allows both animations (the growing progress bar and changing background color) to follow the same scroll behavior, even though they are separate elements and animations. The timeline-scope property What if we wanted to animate a certain element based on a scroll position of an older sibling, or even a parent? The timeline-scope property is what comes into play. It allows us extend the scope of the scroll-timeline to the subtree of the current element. The value for timeline-scope is a custom identifier. This is again a dashed ident. Let’s look at a new example to illustrate this. We can play the animation on the image when scrolling the text container because they are siblings in HTML structure: Long text… Here, only.scroll-container has scrollable content. Let’s start by naming this:.scroll-container /*… */ overflow-y: scroll; Scroll-timeline – –containerText We can play the animated image when scrolling the container text because they are siblings. Long text… Here only the.scroll has scrollable content. Let’s name this:.scroll /*… */ scroll; scroll-timeline –containerText. Let’s now move on to the image within the sardinas container. We want to make this image animate when we scroll through the scroll container. I’ve added the scroll-timeline name to its animation property:.sardinascontainer img. /*…*/ animation: moveUp step(6) both; Animation-timeline: –containerText. However, at this point, the animation will still not work because the scroll container is not directly related with the images. We need to extend the Scroll-Timeline-Name so that it is reachable. This is achieved by adding the timeline scope to the parent element shared by both elements..main-container: /*…*/ timeline-scope –containerText. After learning how to use the timeline-scope, let’s move on to the second type of scroll-driven animated. The properties are the same, but the behavior is slightly different. View Progress Timelines Scroll progress animations were the last thing we looked at. This is the first of two types of scroll-driven animated. We’ll now turn our attention to the view progress animations. Both have many similarities! They’re both different enough that we need to have their own section to learn how they work. These are also called view-timeline or view progress animations because they revolve around the view() function. The view progress timeline represents the second type scroll-driven type of animation we’re going to look at. It tracks an element when it enters or leaves the scrollport (the visible portion of the scrollable content). This behavior is very similar to the IntersectionObserver in JavaScript, but can be done completely in CSS. We have anonymous view progress timelines and named scroll animations. Let’s take a look at them. Anonymous View Timeline Here is a simple example that will help us understand the basic idea behind anonymous view timelines. View Timeline Animation by Mariana Beldi shows how the image fades in when you scroll to a specific point on the page. Let’s say that we want to animate a fade-in image as it appears on the scrollport. The image’s transparency will change from 0 to 1 You could also use @keyframes to create the same animation using classic CSS: img animation: fadeIn 1s @keyframes fadeIn from: opacity 0 to: opacity 1; The animation would be like a tree falling in a forest without anyone there to witness it… Did the animation happen? We’ll never find out! We can use the view() function to create a view progression animation in a single CSS line: img animation: fadeIn; Animation-timeline: View(); Notice that we don’t need to declare a duration for animations like we used to do with classic CSS. The animation is now not tied to time, but rather by space. The animation is triggered when the image appears in the scrollport. View Timeline Parameters Like the scroll-timeline, the view timeline accepts parameters to allow for more customization. Animation-timeline: view ();Controls the start and end of the animation relative to the element’s visible position within the scrollport. It defines the distance between the edges of scrollport and the element to be tracked. The default value of auto can also be replaced with start and end values, as well as length percentages.
This parameter is similar to that of the scroll-timeline axis parameter. This parameter specifies which axis the animation is bound to (horizontal or Vertical). The default setting is block which tracks vertical movement. You can also use inline for horizontal movement, or simple x and y. Here is an example where you can customize when and how animation starts using both inset and inset: img animation-timeline: axis(20% block); In this case the animation begins when the image appears 20% in the scrollport. The animation is triggered when the image is scrolled vertically (block axis). The view() function makes it easy to create parallax effect by simply adjusting animation properties. You can, for example, have an element scale or move as it enters a scrollport without JavaScript: img animation: parallaxMove1s; animation-timeline view(); @keyframes: parallaxMove to transform: translateY (-50px); View the Pen Parallax animations with CSS Scroll by Mariana Beldi. The animation-range Property By using the CSS animation range property with view timelines, you can define how much an element’s visible area within the scrollport determines the start and finish points of the animation. This property can be used to fine tune when the animation starts and ends depending on the element’s visible in the viewport. The default value of normal in view timelines means that the element is tracked from the moment it enters the scrollport to the time it leaves. This is represented by the following: animation-range: normal normal; /* Equivalent to */ animation-range: cover 0% cover 100%; Or, more simply: animation-range: cover; There are six possible values or timeline-range-names: cover
Tracks the visibility of an element from the moment it enters the scrollport until it leaves. contain
Tracks the time when the element becomes fully visible within the scrollport. Entry
Tracks the element as it enters the scrollport. Exit
Tracks the element starting at the point where it begins, leaving the scrollport and continuing until it is fully outside. entry-crossing
Tracks the element from the start of the scrollport to the full crossing. exit-crossing
Tracks the element from the start of its crossing to the full crossing. You can use different timeline-range names to control the start-end points of the range. You can combine these values to define custom behavior. For example, you can start the animation when the element enters and ends it when it leaves the scrollport: animation range: entry exit. You can also use percentages to define custom behavior. It is easier to understand with tools like Bramus’ view timeline range visualizer. The ability to use timeline-range names inside @keyframes is one of the most powerful features. See the Pen “Target range inside @keyframes” – view-timeline and timeline-range name [forked] Mariana Beldi. In that demo, two different animations occur: slideIn
The element becomes visible when it enters the scrollport. SlideOut
When the element leaves, its scale is reduced and it fades out. @keyframes slideIn from transform: scale(.8) translateY(100px); opacity: 0; to transform: scale(1) translateY(0); opacity: 1; @keyframes slideOut from transform: scale(1) translateY(0); opacity: 1; to transform: scale(.8) translateY(-100px); opacity: 0 The new thing is that now we can merge these two animations using the entry and exit timeline-range-names, simplifying it into one animation that handles both cases: @keyframes slideInOut /* Animation for when the element enters the scrollport */ entry 0% transform: scale(.8) translateY(100px); opacity: 0; entry 100% transform: scale(1) translateY(0); opacity: 1; /* Animation for when the element exits the scrollport */ exit 0% transform: scale(1) translateY(0); opacity: 1; exit 100% transform: scale(.8) translateY(-100px); opacity: 0; entry 0%
Defines the state at the start of the element’s entry into the scrollport. (scaled down, transparent). Entry 100%
Defines when the element has entered the scrollport fully (fully visible and sized up). Exit 0%
Tracks the element as soon as it leaves the scrollport. Exit 100%
Defines when the element leaves the scrollport completely (scaled down, transparent). This allows us to animate an element’s behavior as it enters and exits the scrollport within a single @keyframes. Named view time line and timeline scope The concept of using a view-timeline to link named timelines across different elements is a great way to expand the possibilities of scroll-driven animations. By using a named timeline and view-timeline, we can link the scroll-driven animated images with the animations for unrelated paragraphs within the DOM structure. The view-timeline works in a similar way to the scroll-timeline. It’s a shorthand way to declare the view-timeline name and view-timeline axis properties on one line. The difference between scroll-timeline and this method is that we can now link the animations of elements when they enter the scrollport. I added an animation to each paragraph of the previous demo so that you can see the opacity changing when scrolling images on the left. See the Pen View Timeline, Timeline-scope [forked] Mariana Beldi. This example is a bit verbose but I couldn’t think of a better way to demonstrate its power. Each image in the vertical scroll container is assigned a named view-timeline with a unique 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 each image have its own custom name, such as –one for the first image, –two for the second, and so on. Then, we link the animations in the paragraphs with the named timelines for the images. The corresponding paragraph will animate when images 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 types(4) Animation-timeline : –four This allows the named time lines (—-one, —-two, —-four, etc.) to be shared and referenced between the elements..porto /*… / timeline-scope : –one —–two —–three –four; Final Notes Today in December 2024, we’ve covered most of the CSS Scroll-Driven Animations Module Leve 1 specifications. I’d like to highlight some key takeaways from the spec that helped me better grasp these new rules. Scroll container essentials
It may seem obvious but a scroll container must be present for scroll-driven animated to work. When elements such as text or containers are resized, or when animations on larger screens are tested, the scrollable area disappears. Impact of absolute position
Absolute positioning can sometimes interfere in the intended behavior of animations that scroll. When position: absolute is used, the relationship between elements and parent elements becomes complicated. Tracking the initial state of an element
Before any transformations are applied (like translate), the browser evaluates an element’s current state. This can affect when animations, and in particular view timelines, start. Due to the initial state, your animation may trigger earlier or later than you expected. Avoid hiding overflow
Overflow: hidden can interfere with the scroll-seeking mechanisms in scroll-driven animated. The recommended solution is switching to overflow: clip. Bramus has an excellent article on this topic and a video by Kevin Powell suggests that we might not need overflow: hidden. Performance
To get the best results, use GPU-friendly properties such as transforms, opacity and some filters. This avoids the heavy lifting of recalculating the layout and repainting. Animating things like box-shadow, width, or height can slow down the process because they require a re-rendering. Bramus said that soon more properties, such as background-color, clippath, width and height, will be able to be animated on the compositor. This will improve performance. Use will-change with caution
Use this property sparingly to promote elements towards the GPU. Overusing will-change may lead to excessive memory consumption, as the browser reserves resources despite the fact that animations are not frequently changing. Order is important
Always place the animation timeline after the shorthand animation. Accessibility and progressive enhancement
For example: @media screen and (prefers-reduce-motion: no preference) @supports ((animation-timeline: scroll()) and (animation range 0% 100%) .my class animation moveCard linear both; For example: @media screen and (prefers-reduce-motion: no-preference) { @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) { .my-class { animation: moveCard linear both;
animation-timeline: view(); } } My main struggle while trying to build the demos was more about CSS itself than the scroll animations.|Animation-timeline: view() The CSS was the main problem I had when trying to build these demos. Not the scroll animations.} Often, generating the layout was more difficult than applying scroll animation. Some things that I was confused about at first are no longer there. Remember, the spec has been in development for over five years! X and Y axes
The old “horizontal” or “vertical” axes are no longer supported by Firefox, but it has been updated. Old @scrolltimeline syntax
Scroll timelines were previously declared using @scroll timeline, but that has changed with the latest version of the specification. Scroll-driven animations vs. scroll linked animations
Scroll-linked animations was the original name for scroll-driven animations. Double-check that the article has been updated with the latest specification if you see this older term, especially for features like timeline-scope. Resources This collection contains all the demos that were used in this article. I may add more as I continue to experiment. This is a collection of demos I found interesting on CodePen (send me any you’d like to include!). This GitHub repository is where you can join discussions or report issues about scroll-driven animated. Bramus Google Chrome tutorial includes demos, tools and videos.