The Future of CSS: Scroll-Linked Animations (Part 1)

The “Scroll-linked Animations Specification” is an upcoming addition to CSS that defines a way for creating animations that are linked to a scroll offset of a scroll container. Let’s take a look at how it works and what results we can achieve with it.


This content originally appeared on Bram.us and was authored by Bramus!

The Scroll-linked Animations Specification is an upcoming addition to CSS that defines a way for creating animations that are linked to a scroll offset of a scroll container. Even though the specification is still in draft, and in no way finalized nor official, it already has experimental support in Chromium.

The past few weeks I’ve been playing with the CSS @scroll-timeline at-rule and animation-timeline CSS property this specification provides. By combining these two features with regular CSS Animations we can create Scroll-Linked Animations using only CSS — not a single line of JavaScript in sight!

In this first part of this series we’ll take a look at Scroll-Linked Animations between two absolute scroll-offsets, and how we can tweak them. In the second part of this series (published later this week) we’ll cover how to create Scroll-Linked Animations based on the location of an element within the scroller.

~

?‍? The CSS features described in this post are still experimental and not finalized at all! If you’re feeling adventurous you can play with these new features today, but you’ll need at least Chromium 89 with the #experimental-web-platform-features flag enabled through chrome://flags.

? To keep your primary Chrome install clean, I recommend you do not set this in Chrome Stable, but resort to Beta / Canary builds.

? If you don’t understand how to do this, or don’t feel safe doing this, fear not: This post also includes recordings and/or fallback versions using JavaScript for most of the demos.

? While the Scroll-Linked Animations Specification also describes a JavaScript interface, the main focus of this post will be its CSS counterpart. The JS alternatives won’t be covered in detail.

~

Table of Contents

  1. Primer: Scroll-Linked Animations vs. Scroll-Triggered Animations
  2. Your first Scroll-Linked Animation (Progress Bar Demo)
  3. Tweaking the Offsets (Parallax Cover Demo)
  4. Changing the Scroll Orientation
  5. Changing the Scroll Container (In-Page Gallery Demo)
  6. In-Between Summary
  7. More Demos
    1. Parallax Cover to Sticky Header Demo
    2. Full Screen Panels with Snap Points Demo
    3. Full Screen Panels with Snap Points Demo, With Navigation Controls
  8. In Closing

~

# Primer: Scroll-Linked Animations vs. Scroll-Triggered Animations

Before we jump into the CSS code, there’s this difference that we need to make between Scroll-Linked Animations and Scroll-Triggered Animations

Scroll-Linked Animations are animations are linked to the scroll offset of a scroll container. As you scroll back and forth the scroll container, you will see the animation timeline advance or rewind as you do so. If you stop scrolling, the animation will also stop.

Think of a progress bar shown on top of a page, where there is a direct link between the scroll progress and size of the progress bar. Hit the ⏮ and ⏭ buttons in the visualization below to see how it behaves.

See the Pen Scroll-Linked Animations Visualization: Progressbar by Bramus (@bramus) on CodePen.

Using Scroll-Linked Animations you can animate elements as a scroll container scrolls.

Scroll-Triggered Animations are animations that are triggered when scrolling past a certain position. Once triggered, these animations start and finish on their own, independent of whether you keep scrolling or not.

Think of those typical “content flies in as it enters the viewport” animations. Hit the ⏮ and ⏭ buttons in the visualization below to see how it behaves.

See the Pen Scroll-Triggered Animations Visualization: Fly-In Content by Bramus (@bramus) on CodePen.

Using Scroll-Triggered Animations you can animate elements as they enter/exit the scrollport

~

# Your first Scroll-Linked Animation (Progress Bar Demo)

Let’s take a look at how a Progress Bar is implemented with Scroll-Linked Animations, and dissect it from there.

~

Apart from positioning and what not, the code that drives this demo is this little piece of CSS:

/* (1) Define Keyframes */
@keyframes adjust-progressbar {
    from {
        transform: scaleX(0);
    }
    to {
        transform: scaleX(1);
    }
}

/* (2) Define a ScrollTimeline */
@scroll-timeline progressbar-timeline {
    time-range: 1s;
}

/* (3) Attach the Animation + set the ScrollTimeline as the driver for the Animation */
#progressbar {
    animation: 1s linear forwards adjust-progressbar;
    animation-timeline: progressbar-timeline; /* ? THIS! */
}

We recognise 3 key components that we need to make it all work:

  1. An Animation
  2. A Scroll Timeline
  3. A way to link both

~

# The Animation

This is a a regular CSS Animation. In case of our progress bar it’s an animation that goes from zero width to full width.

@keyframes adjust-progressbar {
    from {
        transform: scaleX(0);
    }
    to {
        transform: scaleX(1);
    }
}

#progressbar {
    width: 100vw;
    transform: scaleX(0);
    transform-origin: 0 50%;
    animation: 1s linear forwards adjust-progressbar;
}

There’s a few things to note about this animation:

  • To optimize this animation for the browser we don’t animate the width property, but fixate the width to 100vw and animate transform: scaleX(…); instead. To make that work properly we have to set the transform-origin to the left edge of the element.
  • To prevent a FOUC we apply the start scaleX(0); transform directly onto the #progressbar element.
  • To make sure this animation remains in its end state when it has finished, we set animation-fill-mode to forwards.
  • The values for animation-duration and animation-timing-function look like they are chosen arbitrarily here, but they’re not. We’ll dig into these further down.

Now, if you implement this piece of CSS as-is, you’ll see this animation runs all by itself. This is because we have not created nor linked a Scroll Timeline yet, which is the following step.

~

# The Scroll Timeline

As we scroll through the document from top to bottom (e.g. from 0% to 100%) we want our animation to also go from start to finish (e.g. from 0s to 1s). For this we need a Scroll Timeline, which is a timeline whose actual time value is determined by the progress of scrolling in a scroll container.

To define a ScrollTimeline in CSS, we can use the new @scroll-timeline at-rule, and configure it using descriptors:

  • source
  • orientation
  • scroll-offsets
  • time-range

We will cover each and every one them in this post. For our Progress Bar we will start with the time-range descriptor, as that’s the only descriptor we need to make it work:

@scroll-timeline progress-timeline {
    time-range: 1s;
}

The time-range descriptor takes a CSS <time> Data Type as it value. However, it does not represent the time of a clock, but is something that maps Scroll Progress to Animation Progress. It gives an answer to the question “How much time should pass when we advance from start to finish in the timeline?”

The scroll-to-time mapping we want looks like this:

  • 0% Scroll Progress should equal 0s Animation Progress.
  • 100% Scroll Progress should equal 1s Animation Progress.

(All values in between are interpolated, e.g. 50% Scroll Progress will equal 0.5s Animation Progress)

As we have defined our animation-duration to be 1s from start to finish, we want our time-range to reflect the same duration, namely 1s: Scrolling from top to bottom (e.g. from 0% to 100%) should advance the animation by 1s

? TIP: Always set time-range to the exact same time as the animation-duration, unless you have a very good reason not to.

? It’s very important to understand that this time-range descriptor does not represent the time of a clock, but is nothing more than a mapping.

A few examples to make this more clear:

  • Say animation-duration + time-range are both 1s:

    • 0% of scrolling maps to 0s of the timeline
    • 100% of scrolling maps to 1s of the timeline

    ? In this case the animation will go from start to finish a you scroll from top to bottom.

  • Say animation-duration is 1s and time-range is 2s:

    • 0% of scrolling maps to 0s of the timeline
    • 100% of scrolling maps to 2s of the timeline

    ? Here the animation will go twice as fast as you scroll to the bottom. E.g. if you’re halfway in the scroll container, the animation will already be complete!

~

# Linking up both

To associate our @scroll-timeline with our CSS Animation we can use the new animation-timeline property.

#progressbar {
    animation: 1s linear forwards adjust-progressbar;
    animation-timeline: progressbar-timeline; /* ? THIS! */
}

This is the part where our animation-timing value of linear comes into play: if we were to set our timing to something like ease-in, we’d see our progress bar be too slow at the beginning and speed up towards the end as we scroll. This feels really weird to be honest. Therefore we need linear, as it enforces a 1-on-1 mapping between Scroll Progress and Animation Progress.

? TIP: Always set animation-timing-function to linear when working with @scroll-timeline.

~

# Tweaking the Offsets (Parallax Cover Demo)

By default a @scroll-timeline will be linked to the scrolling vertically from top to bottom across the document. But what if we don’t want to scroll from top to bottom, but only for a specific amount? This is where the scroll-offsets descriptor comes into play.

Take the page below, where we have a full-page parallax cover. Here we don’t want the document to have scrolled 100% of the entire document for the animation to finish, but want the animation to finish after a scroll distance of 100vh

Our resulting @scroll-timeline looks like this:

@scroll-timeline parallax-header-timeline {
    scroll-offsets: 0%, 100vh;
    time-range: 1s;
}

You can put any <length> or <percentage> Data Type in there.

☝️ In an earlier version of the spec one had to define the Scroll Offsets using from and to descriptors.

@scroll-timeline parallax-header-timeline {
    from: 0%;
    to: 100vh;
    time-range: 1s;
}

This is no longer the case, and one should now use the scroll-offsets descriptor instead.

However, you might still see this older syntax in the demos as Chromium has this older version implemented and is in the process of migrating to the new scroll-offsets syntax — Relevant Chromium Bug: 1094014

If you want, you can also put in more than two values, but note that your scroll to time mapping might become wonky. With scroll-offsets: 0vh, 80vh, 100vh; and a time-range of 1s for example, your scroll-time map will become this:

  • At 0vh your time-range will have advanced to 0s
  • At 80vh your time-range will have advanced to 0.5s, as that 80vh is defined “halfway the array of values”
  • At 100vh your time-range will have advanced to 1s
? TIP: Always set two values for scroll-offsets, unless you have a specific reason not to.

☝️ The scroll-offsets can accept more types of values, which we will cover further down this post.

~

# Changing the Scroll Orientation

By default a @scroll-timeline will be linked to the scrolling vertically from top to bottom across the document. Using the orientation descriptor we can change this to — for example — horizontal.

@scroll-timeline example {
    orientation: horizontal;
    time-range: 1s;
}

Use of the logical values inline and block is also allowed. Finally, there’s also auto.

~

# Changing the Scroll Container (In-Page Gallery Demo)

By default a @scroll-timeline will be linked to the scrolling vertically from top to bottom across the document. But what if we don’t want across the document, but across a specific element? This is where the source descriptor comes into play.

Below is an example that contains two in-page image galleries/carousels, implemented using scroll-snapping. Each of those have a progress bar attached. To drive these progress bars we need not want to respond for scroll progress on the document, but while scrolling in their own scroll container.

To define which scroll container a @scroll-timeline responds to, you need set the source descriptor targeting said element. To do so you can use the selector() function for its value. That function requires an <id-selector>, so you’ll need to give your targeted element an id attribute value.

@scroll-timeline example {
    source: selector(#foo);
    time-range: 1s;
}

As we have two galleries, we need to define two @scroll-timeline instances and connect them to their proper progress bar. And since they are horizontally scrolling ones, we also need to set the orientation descriptor correctly. Our code eventually looks like this:

<div class="gallery" id="gallery1">
    <div class="gallery__progress" id="gallery1__progress"></div>
    <div class="gallery__scrollcontainer" id="gallery1__scrollcontainer">
        <div class="gallery__entry">
            …
        </div>
        <div class="gallery__entry">
            …
        </div>
    </div>
</div>
@keyframes progress {
	to {
		transform: scaleX(1);
	}
}

/* #gallery1 */
@scroll-timeline gallery1__timeline {
	source: selector(#gallery1__scrollcontainer);
	orientation: horizontal;
	time-range: 1s;
}
#gallery1__progress {
	/* We have 2 photos, with the 1st visible, so we start at 1/2 */
	transform: scaleX(0.5);
	animation: 1s linear forwards progress;
	animation-timeline: gallery1__timeline;
}

/* #gallery2 */
@scroll-timeline gallery2__timeline {
	source: selector(#gallery2__scrollcontainer);
	orientation: horizontal;
	time-range: 1s;
}
#gallery2__progress {
	/* We have 3 photos, with the 1st visible, so we start at 1/3 */
	transform: scaleX(0.333);
	animation: 1s linear forwards progress;
	animation-timeline: gallery2__timeline;
}

? One thing I find pretty annoying when it comes to this selector() function is that you must pass an id into it. This can become pretty cumbersome: with 10 galleries on a page, you need to define 10 almost identical @scroll-timelines in your code. Only difference between them: the id passed into selector().

I consider this to be shortcoming of the specification, and have raised an issue with the CSSWG: it would be handy if selector() could point to the current element being animated or would accept any selector. That way you can reuse one single @scroll-timeline on multiple elements.

Relevant CSS WG Issue: 5884

? If you think you would be able to dynamically set the <id-selector> in source by means of CSS Custom Property, don’t bother: CSS Variables cannot be used within descriptors.

~

# In-Between Summary

? Before we continue with the really cool stuff that’s coming up, let’s summarize what we know so far.

A Scroll Timeline is an interface that lets us map Scroll Progress to Animation Progress. You can define it in CSS using @scroll-timeline with the following descriptors:

source
The scrollable element whose scrolling triggers the activation and drives the progress of the timeline.
orientation
The direction of scrolling which triggers the activation and drives the progress of the timeline.
scroll-offsets
An array of two or more scroll offsets that constitute the in-progress intervals in which the timeline is active.
time-range
A duration that maps the amount scrolled between the Scroll Offsets to the duration of an animation.

Allowed values for the descriptors:

  • By default the source is the document’s scrolling element (value: auto), but you can also target an element using selector(<id-selector>)
  • The orientation is vertical or horizontal. Using logical units inline and block is also possible. The initial value is auto.
  • Typically the entries in scroll-offsets are lengths or percentages, but we’ll cover an extra variation in the next part
  • Easiest setting for time-range is the same value as the animation’s animation-duration.

To attach a @scroll-timeline to an animation, use the animation-timeline property.

~

# More Demos

As I have been playing with CSS @scroll-timeline for nearly a month by now, I’ve been making quite a lot of demos. Here’s a fine selection relevant for this first part of this series:

  1. Parallax Cover to Sticky Header Demo
  2. Full Screen Panels with Snap Points Demo
  3. Full Screen Panels with Snap Points Demo, With Navigation Controls

~

# Parallax Cover to Sticky Header Demo

Building further upon the Parallax Cover from earlier on, here’s a page that converts a full pace Cover Screen to a Sticky Header.

The @scroll-timeline is exactly the same as the Parallax Cover demo, only the animation is a bit different: the color, font-size, and height are also adjusted upon scrolling.

Couldn’t use position: sticky; here though, as resizing the cover would shrink down the entire height of the document, and therefore the animation would flicker. Instead I resorted to position: fixed; and added a margin-top of 100vh to the text content so that it remains visually below the cover.

~

# Full Screen Panels with Snap Points Demo

This is a small demo forked from this demo by Adam Argyle, which put CSS @scroll-timeline on my radar (thanks, Adam!). The page features a full-page carousel with numbers that slide into view.

The demo has been adjusted to use CSS @scroll-timeline and mix-blend-mode: difference;.

The / 4 suffix is position: fixed; on the page, and the / character inside spins around 1turn per page that you scroll. As are four sections in total, we spin for a total of 3turn from start to finish.

@scroll-timeline spin-slash {
  source: selector(#main);  
  time-range: 1s;
}

@keyframes rotato {
  to {
    transform: rotateZ(3turn);
  }
}

.slash {
  animation: rotato 1s linear;
  animation-timeline: spin-slash;
}

~

# Full Screen Panels with Snap Points Demo, With Navigation Controls

This demo builds further upon the previous one and adds a navigation bar to it. The active indicator is powered by @scroll-timeline: as you scroll through #main, the active indicator moves to the correct navigation item.

There are two variants for you to check:

  1. There is one single active indicator shared amongst all navigation items.
  2. Each navigation item has its own active indicator.

I like how in this second example these indicators reflect the percentage each section is in view (or not).

In the first version a line is injected underneath the navigation and its left position is adjusted using the same @scroll-timeline as the panels use.

In the second version each navigation item gets a line injected. The animation to show/hide the line is one shared animation for all items that does both the showing and the hiding:

@keyframes reveal-indicator {
  1% { /* We use 1% instead of 0% to prevent rounding/rendering glitches */
    transform: scaleX(0);
  }
  50% {
    transform: scaleX(1);
  }
  99% {  /* We use 99% instead of 100% to prevent rounding/rendering glitches */
    transform: scaleX(0);
  }
}

Now it gets tricky though: for each navigation item we create a different @scroll-timeline whose scroll-offsets and time-range vary.

  • The default time-range is 4s
  • The first and last items only need half an animation though (as you can’t scroll past them) so their time-range is set to 2s
  • To fix the first item’s animation we use a negative animation-delay of -2s on the element itself. That way it’s animation will start “too soon”, and will already be at 50% (thus at scaleX(1)) on page load.

~

# In Closing

That’s it for the first part of this series! We’ve covered how to create Scroll-Linked Animations between two absolute scroll-offsets, and how we can tweak our defined @scroll-timelines.

? In the second part of this series (published later this week) we’ll cover how to create Scroll-Linked Animations based on the location of an element within the scroller.

Follow @bramus (= me, the author) and or @bramusblog (= the feed of this blog) on Twitter to get notified. RSS also available.

I hope I’ve been able to get you excited for this possible future addition to CSS throughout this post. Although it still is in its very early stages, I’m confident this will become a CSS WG Recommendation one day ?

I’m glad to see that the Chromium engineers are actively working on this experimental implementation, taking the time to respond to newly reported bugs. I hope that other browser vendors will follow suit soon. Relevant tracking bugs to flag/star/follow:

? You can find all demos shown in this post over at CodePen, in a Collection Scroll-Linked Animations: Part 1. It’d be great if you could ❤️ the collection and/or the demos you like.

~

To help spread the contents of this post, feel free to retweet its announcement tweet:

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.


This content originally appeared on Bram.us and was authored by Bramus!


Print Share Comment Cite Upload Translate Updates
APA

Bramus! | Sciencx (2021-02-23T00:07:49+00:00) The Future of CSS: Scroll-Linked Animations (Part 1). Retrieved from https://www.scien.cx/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/

MLA
" » The Future of CSS: Scroll-Linked Animations (Part 1)." Bramus! | Sciencx - Tuesday February 23, 2021, https://www.scien.cx/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/
HARVARD
Bramus! | Sciencx Tuesday February 23, 2021 » The Future of CSS: Scroll-Linked Animations (Part 1)., viewed ,<https://www.scien.cx/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/>
VANCOUVER
Bramus! | Sciencx - » The Future of CSS: Scroll-Linked Animations (Part 1). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/
CHICAGO
" » The Future of CSS: Scroll-Linked Animations (Part 1)." Bramus! | Sciencx - Accessed . https://www.scien.cx/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/
IEEE
" » The Future of CSS: Scroll-Linked Animations (Part 1)." Bramus! | Sciencx [Online]. Available: https://www.scien.cx/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/. [Accessed: ]
rf:citation
» The Future of CSS: Scroll-Linked Animations (Part 1) | Bramus! | Sciencx | https://www.scien.cx/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.