Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time

import Fireworks from 'components/papers/Fireworks' export const meta = require('./metadata.json')

It's hard to innovate in the web world. There's a combination of well-known patterns and technical limitations that constrain the possibilities. The businesses are afraid of big changes because consolidated UX helps to keep the users engaged. And technical aspects like the form factor, keyboards, mouses, etc. Limit how we interact with digital products.

However, sometimes small yet powerful changes happen. Nothing as disruptive as the introduction of the touch screen. But meaningfully nonetheless. For example, the uber-famous hamburger button. It's not a crazy innovation. Not at all. But it's a great improvement for toggling side menus. Nowadays, it's the de facto solution. All the users over the world understand what this icon means. And even companies like Google included this in their official design guidelines.

Today, we're talking about another of these little giants changes. Scroll-based animations. They're not new; of course not. But now we know how to run smooth 60 fps scroll animations. And these look dope.

You probably have seen them in some product landing pages. Such as the new Airpods Pro from Apple. It's slick, responsive and creates a stunning visual. So, how does this work?

The abstract idea is to animate using a sequence of images in rapid succession. You know, like a flip book!

Animated flipbook

To be honest, it's not the most obvious solution. You may think about <img />, some scroll event hijacking and some offset calculations. Wrong! If you do it that way everything will end up being clunky and messy. Instead, we're gonna follow these golden rules:

  1. canvas over img. Long story short, the canvas can render changes way faster than img. The classic image tag is for static views.

  2. Preload the assets. This is mandatory. Otherwise, on every render it'll download the image. So, it'll look clunky.

  3. Do not hijack the scroll event. Instead, fix or stick the canvas on a long-height component.

And a few drawbacks:

  1. You're gonna be fetching hundreds of images. Consider a good compression and even checking the network first to see if it's worth downloading for your user. To be fair, this technique is how video works; but lighter. For example, the gif above weights almost 5MB and it's highly compressed and low quality. Instead, the whole set of images for the demo weights 8MB but every pic is 2560x1440px in jpeg format. So, it's not that bad.

  2. The most important bit for this visual to work are the images. You'll need a high-quality sequence of photos before implementing.

With the proper instructions applied you can easily get something like this.

Enough talking. Let's see some code. First, we need a basic markup:

<!-- Preload -->
<head>
  <!-- Repeat for as many frames as needed -->
  <link
    rel="preload"
    as="image"
    type="image/jpeg"
    href="/asset/frame/001.jpeg"
  />
</head>

<!-- Markup -->
<div class="container">
  <canvas></canvas>
</div>

And then a bit of JavaScript to handle the render animation.

const context = canvas.getContext('2d')
const image = new Image()
const frameMax = 174

$container.addEventListener('scroll', () => {
  const scrollTop = $container.scrollTop
  const maxScrollTop = $container.scrollHeight - window.innerHeight
  const scrollFraction = scrollTop / maxScrollTop
  const current = Math.floor(scrollFraction * frameMax)
  const frame = Math.min(frameMax - 1, current)

  requestAnimationFrame(() => {
    // Update every frame
    image.src = createFrameRoute(frame + 1)
    context.drawImage(image, 0, 0)
  })
})

Notice the requestAnimationFrame (RAF). This is the key concept on the JavaScript side. It allows the animation to be smooth even when the website main thread is heavy on other stuff.

Finally, we need to add some spicy CSS.

.container {
  /* The long-height container to  */
  height: 600vh;
  position: relative;
}

canvas {
  max-height: 100vh;
  max-width: 100vw;
  /* Keep the canvas in front of the container while scrolling */
  position: sticky;
  top: 0;
}

We're not gonna focus on the details of how sticky works. For such will be another paper. It'll depend on your layout needs, as well. So the best you can do is try yourself!