Skip to main content

Scroll Animation

There are some scroll animations that are possible in CSS without any JavaScript at all. Just look at the chapter on the Scroll Indicator, which is clearly CSS magic. But we can do a lot of scroll animation work directly in CSS with just one little bit of information provided by JavaScript: how far the page has scrolled.

So let’s get that out of the way. With a JavaScript one-liner, we can set a CSS custom property that knows the percentage of the page scrolled:

window.addEventListener('scroll', () => {
  document.body.style.setProperty('--scroll',window.pageYOffset / (document.body.offsetHeight - window.innerHeight));
}, false);

Now we have --scroll as a value we can use in the CSS.

This trick comes by way of Scott Kellum who is quite the CSS trickery master!

Let’s set up an animation without using that value at first. This is a simple spinning animation for an SVG element that will spin and spin forever:

svg {
  display: inline-block;
  animation: rotate 1s linear infinite;
}

@keyframes rotate {
  to {
    transform: rotate(360deg);
  }
}

Here comes the trick! Now let’s pause this animation. Rather than animate it over a time period, we’ll animate it via the scroll position by adjusting the animation-delayas the page scrolls. If the animation-duration is 1s, that means scrolling the whole length of the page. is one iteration of the animation.

svg {
  position: fixed; /* make sure it stays put so we can see it! */

  animation: rotate 1s linear infinite;
  animation-play-state: paused;
  animation-delay: calc(var(--scroll) * -1s);
}

Try changing the animation-duration to 0.5s. That allows for two animation can be completed with the animation-delay math.

Scott noted in his original demo that also setting:

animation-iteration-count: 1;
animation-fill-mode: both;

Accounted for some “overshoot” weirdness and I can attest that I’ve seen it too, particularly on short viewports, so it’s worth setting these too.

Scott also set the scroll-related animation properties on the :root {} itself, meaning that it could control all the animations on the page at once. Here’s his demo that controls three animations simultaneously: