JavaScript Animations

(Heavily from https://javascript.info/js-animation, and Mozila Development Network)

Using setInterval

An animation can be implemented as a sequence of frames – usually small changes to HTML/CSS properties.

For instance, changing style.left from 0px to 100px moves the element. And if we increase it in setInterval, changing by 2px with a tiny delay, like 50 times per second, then it looks smooth. That's the same principle as in the cinema: 24 frames per second is enough to make it look smooth.

The pseudo-code can look like this:

let timer = setInterval(function() {
  if (animation complete) clearInterval(timer);
  else increase style.left by 2px
}, 20); // change by 2px every 20ms, about 50 frames per second

A more complete example of the animation:

let start = Date.now(); // remember start time

let timer = setInterval(function() {
  // how much time passed from the start?
  let timePassed = Date.now() - start;

  if (timePassed >= 2000) {
    clearInterval(timer); // finish the animation after 2 seconds
    return;
  }

  // draw the animation at the moment timePassed
  draw(timePassed);

}, 20);

// as timePassed goes from 0 to 2000
// left gets values from 0px to 400px
function draw(timePassed) {
  train.style.left = timePassed / 5 + 'px';
}

The Window.setTimeout() Method

(From https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout)

The setTimeout() method of the Window interface sets a timer which executes a function or specified piece of code once the timer expires.

setTimeout(code) // not recommended
setTimeout(code, delay) // not recommended

setTimeout(functionRef)
setTimeout(functionRef, delay)
setTimeout(functionRef, delay, param1)
setTimeout(functionRef, delay, param1, param2)
setTimeout(functionRef, delay, param1, param2, /* …, */ paramN)

The setTimeout() method returns a positive integer (typically within the range of 1 to 2,147,483,647) that uniquely identifies the timer created by the call. This identifier, often referred to as a timeout ID, can be passed to clearTimeout() to cancel the timer.


If setTimeout() is called with delay value that's not a number, implicit type coercion is silently done on the value to convert it to a number. For example, the following code incorrectly uses the string "1000" for the delay value, rather than the number 1000 – but it nevertheless works, because when the code runs, the string is coerced into the number 1000, and so the code executes 1 second later.

setTimeout(() => {
  console.log("Delayed for 1 second.");
}, "1000");

setTimeout() is an asynchronous function, meaning that the timer function will not pause execution of other functions in the functions stack. In other words, you cannot use setTimeout() to create a pause before the next function in the function stack fires.

Using requestAnimationFrame

Let's imagine we have several animations running simultaneously. If we run them separately, then even though each one has setInterval(..., 20), then the browser would have to repaint much more often than every 20ms. That's because they have different starting time, so every 20ms differs between different animations. The intervals are not aligned. So we'll have several independent runs within 20ms.

In other words, this:

setInterval(function() {
  animate1();
  animate2();
  animate3();
}, 20)

…Is lighter than three independent calls:

setInterval(animate1, 20); // independent animations
setInterval(animate2, 20); // in different places of the script
setInterval(animate3, 20);

These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load the CPU less and make it look smoother.

There's one more thing to keep in mind. Sometimes the CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every 20ms.

But how do we know about that in JavaScript? There's a specification Animation timing that provides the function requestAnimationFrame. It addresses all these issues and even more.

The syntax:

let requestId = requestAnimationFrame(callback)

That schedules the callback function to run in the closest time when the browser is available to do animation.

If we make changes in elements in callback then they will be grouped together with other requestAnimationFrame callbacks and with CSS animations. So there will be one geometry recalculation and repaint instead of many.


The returned value requestId can be used to cancel the call:

// cancel the scheduled execution of callback
cancelAnimationFrame(requestId);

The callback gets one argument – the time passed from the beginning of the page load in milliseconds. This time can also be obtained by calling performance.now().


Usually the callback runs right away, unless the CPU is overloaded or the laptop battery is almost discharged, or for some other reason.

The code below shows the time between first 10 runs for requestAnimationFrame. Usually it's 10-20ms:

let prev = performance.now();
  let times = 0;

  requestAnimationFrame(function measure(time) {
    document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " ");
    prev = time;

    if (times++ < 10) requestAnimationFrame(measure);
  })

Structured animation*

(From Structured Animation, at https://javascript.info)