Animation

If you want to animate things, eg move an element across the screen, you could accomplish it using setTimeout or setInterval (see Timing). Animations done this way won't be great because the redrawing is not synchronised to when the browser refreshes visuals. The result will be a poorly-performing, "janky" animation.

There are two better approaches. The first is to use requestAnimationFrame, the second is to use CSS/HTML Animations. Using the CSS/HTML animations are generally preferable because they offer higher performance and for most cases a better quality result. A third approach is using Web Animations, but we'll skip this here.

requestAnimationFrame

A common use requestAnimationFrame is for animating CANVAS element drawing.

Conceptually, requestAnimationFrame is like setInterval, but the browser calls it in an optimised manner according to the rendering of the page. Because it's meant for things that are manipulating visuals, it is smart enough to start and stop depending on whether the web page visible.

It's as simple as this:

const loop = () => {
  // Code to run
  // ...
  requestAnimationFrame(loop); // Reschedule
}
requestAnimationFrame(loop);

The code you put in the function should be as lean as possible in order to not disrupt rendering. Keep in mind it will be called continuously and at a high rate.

If there's a chance your code throws an error, or you want to be able to exit out of the function earlier, requestAnimationFrame won't be called, so the loop stops. A simple fix is a pattern like this:

const loop = () => {
  // Your code to run
  // ...
}

const runner = () => {
  try {
    loop();
  } catch (e) console.log(e)
  requestAnimationFrame(runner);
}
requestAnimationFrame(runner);

Now loop() will keep running, even if an error is thrown during the process, and you can use return to exit the function whenever you want.

Sliding box

Let's use this technique to smoothly move a box by 1px, stopping after moving it 1000px:

<div id="box">
  BOX
</div>
#box {
  background-color: whitesmoke;
  padding: 1rem;
  position: fixed;
  left: 10px;
}
const loop = () => {
  const el = document.getElementById('box');
  if (el.offsetLeft >= 1000) return; // Stop at some point
  el.style.left = (el.offsetLeft + 1) + "px"
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

See live demo

Bouncing box

Rather than hard-coding 1000px, we can make it respond to the window dimensions, to bounce back and forth.

Since we want to keep track of a few variables associated with the animation, the scheduled function is wrapped in another function to keep it nice and self-contained. By passing in el as a parameter, we can use the same function to animate other elements as well.

const loopAnimation = (el) => {
  const speed = 2; // How many pixels to move with each update
  let delta = speed; // Current movement

  const loop = () => {
    var elRect = el.getBoundingClientRect();
    var newLeft = elRect.left + delta;
    if (newLeft + elRect.width >= window.innerWidth) {
      // Too far right, make it start going the other way
      delta = -speed;
    } else if (newLeft < 0) {
      // Too far left, make it start going the other way
      delta = speed;
    }
    el.style.left = newLeft + "px";
    requestAnimationFrame(loop); // Repeat itself
  }
  // Start it off
  requestAnimationFrame(loop);
}
loopAnimation(document.getElementById('box'));

See live demo

Rotate around centre

In this example, we'll spin an element around the centre of the window. Two helper functions aren't shown here: getCenter and rotate, but you can see the details at the live demo.

loopAnimation = (el) => {
  const origin = getCenter(el); // Use center of box as origin
  // Set radius to be a third of window width or height, whatever is smallest (to keep it on-screen)
  const radius = Math.min(window.innerWidth, window.innerHeight) / 3;
  let angle = 0;
  const loop = () => {
      const pt = rotate(angle, radius, origin);
    el.style.left = pt.left + "px";
    el.style.top = pt.top + "px";
      angle += 0.01;
    if (angle >= 360) angle = 0;
      requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);
}
loopAnimation(document.getElementById('box'));

See live demo

More