Animating on the web - Phelipe Teles

Animating on the web

3 min.
View source code

In this post we’ll explore all the different ways animate on the web, with only CSS and vanilla JavaScript.

For simplicity sake, we’ll animate a box moving forward.

CSS animation property

Let’s start by using the CSS animation property.

This way of animating works by first describing your animation using @keyframes, which is a way to declare the animation frames. For example, the starting styles can be declared under 0% or from and the ending styles with to or 100%.

After defining our keyframe, we use it in the animation property, where we also define the animation duration (1s) and the timing function (linear, in this case).

<!DOCTYPE html>
<html>
  <style>
    .box {
      background-color: black;
      width: 20px;
      aspect-ratio: 1 / 1;
    }

    @keyframes box-animation {
      to {
        transform: translateX(100px);
      }
    }

    .animated-box {
      animation: box-animation 1s linear forwards;
    }
  </style>

  <body>
    <div class="box animated-box"></div>
  </body>
</html>

CSS transition property

Now let’s try doing the same thing with CSS transition property.

With CSS transitions, we no longer have @keyframes, instead we need to pass which property we want to animate to the transition property, while also specifying the transition duration and timing function. Whenever we change the box style, changes to that property value will be animated.

<!DOCTYPE html>
<html>
  <style>
    .box {
      background-color: black;
      width: 20px;
      aspect-ratio: 1 / 1;
      transition: transform 1s linear;
    }

    .animated-box {
      transform: translateX(100px);
    }
  </style>

  <body>
    <div class="box"></div>
  </body>

  <script>
    // We need to force a reflow here for the animation to work
    document.body.getBoundingClientRect()
    document.querySelector('.box').classList.add('animated-box')
  </script>
</html>

In this example, we need a JavaScript trick to make the animation work when the document loads — it doesn’t work with pure HTML and CSS. We need to force a reflow with getBoundingClientRect before adding the animated-box class for the animation to play. If we used the animated-box directly in the HTML, without this trick, the box will not appear moving forward, it will appear already in its destination.

requestAnimationFrame

Now let’s try using the requestAnimationFrame to do the same thing.

requestAnimationFrame is usually not the most convenient way to animate on the web, specially in this case.

This is because requestAnimationFrame is an imperative way to do animations, in contrast with the declarative way of CSS.

For example, there is no parameter to define the animation duration or timing function — requestAnimationFrame just receives a callback function to be executed before the browser updates the screen visually (which is called a repaint), so it’s our job to animate the box from scratch, including for how long and in what way.

We will need to have some kind of infinite loop to repeatedly call the function responsible for the visual changes, but we should these function calls with requestAnimationFrame so our visual updates will only run when the browser is ready to update the screen visually.

This usually means that our function will be called at most 60 times per second. In this way, we can achieve a smooth animation and not waste CPU resources/overload the browser.

In this example, you can see how many times the animation function is run in the console:

<!DOCTYPE html>
<html>
  <style>
    .box {
      background-color: black;
      width: 20px;
      aspect-ratio: 1 / 1;
    }
  </style>

  <body>
    <div class="box"></div>
  </body>

  <script>
    const ANIMATION_DURATION = 1000
    const PIXELS_TO_MOVE = 100

    const box = document.querySelector('.box')

    let start
    let position = 0

    function animateBox(timestamp) {
      console.log('animating box')

      if (start === undefined) {
        start = timestamp
      }

      const elapsed = timestamp - start
      const timeProgress = elapsed / ANIMATION_DURATION

      position = Math.min(PIXELS_TO_MOVE, PIXELS_TO_MOVE * timeProgress)

      box.style.transform = `translateX(${position}px)`

      if (position < PIXELS_TO_MOVE) {
        requestAnimationFrame(animateBox)
      }
    }

    requestAnimationFrame(animateBox)
  </script>
</html>
Show console (0)
No logs yet

Web Animations API

Fortunately, there is a much more convenient JavaScript API to animate, that is similar to how CSS animations work but much more powerful because it’s JavaScript: the Web Animations API.

In the example below, we replicate our animation by calling the animate method of the DOM element and pass an array of objects that represent each step of the animation (this is equivalent to @keyframes in CSS) and in the next object we specify the animation characteristics such as its duration, fill mode (as we’ve seen) etc.

<!DOCTYPE html>
<html>
  <style>
    .box {
      background-color: black;
      width: 20px;
      aspect-ratio: 1 / 1;
    }
  </style>

  <body>
    <div class="box"></div>
  </body>

  <script>
    const box = document.querySelector('.box')

    box.animate([
      {
        transform: 'translateX(100px)',
      }
    ], {
      fill: 'forwards',
      duration: 1000,
    })
  </script>
</html>

View Transitions

We can achieve a similar effect with View Transitions API.

This new browser API is intended to power much more powerful animations, such as page transitions or shared elements. I suggest this blog post if you want to learn more about it.

We can still use it for this blog post’s purpose of animating a box moving — we just need to modify the DOM element to make the box move inside a callback passed to document.startViewTransition:

<!DOCTYPE html>
<html>
  <style>
    .box {
      background-color: black;
      width: 20px;
      aspect-ratio: 1 / 1;
      transition: transform 1s linear;
      view-transition-name: box;
    }

    .animated-box {
      transform: translateX(100px);
    }
  </style>

  <body>
    <div class="box"></div>
  </body>

  <script>
    if (!document.startViewTransition) {
      document.body.innerHTML = 'Your browser does not suppport View Transitions API yet.'
    }

    const box = document.querySelector('.box')
    document.startViewTransition(() => {
      box.classList.add('animated-box')
    })
  </script>
</html>