JavaScript Animations

As you have already learned, CSS animations are used for making simple animations. All the things that CSS can’t handle, you can handle using the JavaScript animations.

For example, you can use JavaScript animations when you want to move along a complex path, with a timing function not similar to the Bezier curve, either an animation on a canvas.

SetInterval

An animation can be performed as an order of frames.

For example, if you change style.left from 0px to 100px, the element will be moved. But when you increase it in setInterval, changing by 2px with a little delay, such as 50 times per second, it will look smooth. Here is the pseudo-code:

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

A more complex example of the animation will look as follows:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <style>
      img {
        position: relative;
        cursor: pointer;
        width: 300px;
        height: 200px;
      }
    </style>
  </head>
  <body>
    <img id="imgId" src="https://ru.w3docs.com/uploads/media/default/0001/05/2c669e35c4c5867094de4bed65034d0cac696df5.png" alt="JS" />
    <script>
      imgId.onclick = function() {
        let start = Date.now();
        let timer = setInterval(function() {
          let timePassed = Date.now() - start;
          imgId.style.left = timePassed / 10 + 'px';
          if(timePassed > 3000) clearInterval(timer);
        }, 20);
      }
    </script>
  </body>
</html>

RequestAnimationFrame

Let’s say multiple animations are running simultaneously. In the event of calling them separately, although each of them has setInterval(..., 20), the browser will have to repaint more often than 20ms.

The reason is that their starting time is different. There are no aligned intervals. Hence, there are different runs within 20ms.

It is demonstrated in the example below:

setInterval(function () {
  animation1();
  animation2();
  animation3();
}, 30)

And, that is lighter than three independent calls, like here:

setInterval(animation1, 30); // independent animations
setInterval(animation2, 30); // in different places of the script
setInterval(animation3, 30);

Another crucial point to keep in mind: at the times when the CPU gets overloaded or there are other reasons, it is not necessary to run every 30ms. If you wonder how you can know about it in JavaScript, then you need to address the requestAnimationFrame function. It can help you solve all these issues and even more.

The syntax of requestAnimationFrame is the following:

let requestId = requestAnimationFrame(callback);

It schedules the callback function to run in the nearest time when the browser intends to perform an animation.

Making changes in the elements of the callback will get them grouped with other requestAnimationFrame and CSS animations.

You can use the returned value requestId for canceling the call, like this:

// cancel scheduled callback execution
 cancelAnimationFrame(requestId);

One argument is received by the callback - the time passed from the start of the page load in microseconds. It can also be obtained with the performance.now() call.

As a rule, the callback runs soon, no matter the CPU is overloaded, the battery of the laptop is almost discharged, and so on. The time between the first ten runs for requestAnimationFrame is demonstrated in the code below:

let prev = performance.now();
let times = 0;
requestAnimationFrame(function measure(time) {
  console.log("beforeEnd", Math.floor(time - prev) + " ");
  prev = time;
  if (times++ < 10){
    requestAnimationFrame(measure);
  }
})

As a rule, the time is 10-20 ms.

Structured Animation

If we try to make a more universal animation, based on requestAnimationFrame, it will look as follows:

function animate({
  timing,
  draw,
  duration
}) {
  let start = performance.now();
  requestAnimationFrame(function animate(time) {
    // fractionOfTime goes from 0 to 1
    let fractionOfTime = (time - start) / duration;
    if (fractionOfTime > 1) {
      fractionOfTime = 1;
    }
    // calculate the current state of the animation
    let progress = timing(fractionOfTime);
    draw(progress); // draw it 
    if (fractionOfTime < 1) {
      requestAnimationFrame(animate);
    }
  });
}

The animate function receives three parameters, essentially describing the animation.

Those parameters are:

  • duration- the total animation time. For example, 1000.
  • timing(timeFraction): it’s like the CSS-property transition-timing-function, which receives the fraction of time that has passed, returning the animation completion. For example, a linear function considers that the animation continues uniformly with the same speed, like here:
    function linear(timeFraction) {
      return timeFraction;
    }
  • draw(progress) - the function, taking the animation completion state and drawing it. The value progress=0 signifies the beginning animation state, and its end is denoted by progress=1. So, this function draws out the animation. The element can be moved by it, like this:
function draw(progress) {
  img.style.left = progress + 'px';
}

Let’s try to animate the width element from 0 to 100% applying the function above.

The demo will look like this:

<!DOCTYPE HTML>
<html>
  <head>
    <title>Title of the Document</title>
    <style>
      progress {
        width: 30%;
      }
    </style>
  </head>
  <body>
    <!--animation script-->
    <script>
      function animate({
        timing, draw, duration
      }) {
        let start = performance.now();
        requestAnimationFrame(function animate(time) {
          let fractionOfTime = (time - start) / duration;
          if(fractionOfTime > 1) {
            fractionOfTime = 1;
          }
          let progress = timing(fractionOfTime);
          draw(progress);
          if(fractionOfTime < 1) {
            requestAnimationFrame(animate);
          }
        });
      }
    </script>
    <progress id="elem"></progress>
    <script>
      elem.onclick = function() {
        animate({
          duration: 500,
          timing: function(fractionOfTime) {
            return fractionOfTime;
          },
          draw: function(progress) {
            elem.style.width = progress * 100 + '%';
          }
        });
      };
    </script>
  </body>
</html>

In contrast with CSS animations, any timing function and any drawing function can be made here. Moreover, the timing function here is not limited by the Bezier curves. The draw may go beyond properties, make new elements for the animation.

Timing Functions

We have already explored the simple, linear timing. Now, we are going to show you movement animations with different timing functions and their usage. To be more precise, we will overview the functions such as the power of n, the arc, bow shooting, the bounce, and the elastic animation.

Bounce

Bouncing is quite common action. You can see it in your everyday life. For instance, if you drop a ball, it will bounce back several times and then will stop. The bounce function acts like that, but in reverse order, the bouncing will start at once. Several unique coefficients are used for it, like this:

function bounce(fractionOfTime) {
  for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
    if (fractionOfTime >= (7 - 4 * a) / 11) {
      return -Math.pow((11 - 6 * a - 11 * fractionOfTime) / 4, 2) + Math.pow(b, 2)
    }
  }
}

EaseOut

The timing function is put into a wrapper timingEaseOut in the “easeOut” mode:

timingEaseOut(fractionOfTime) = 1 - time(1 - fractionOfTime);

That is, there is a makeEaseOut function, which takes a regular timing function, returning the wrapper around it, like this:

// taking a timing function, returning the transformed variant
function makeEaseOut(time) {
  return function (fractionOfTime) {
    return 1 - time(1 - fractionOfTime);
  }
}

Let’s try to take the bounce function and use it:

<!DOCTYPE HTML>
<html>
  <head>
    <title>Title of the Document</title>
    <script src="https://js.cx/libs/animate.js"></script>
    <style>
      #brick {
        width: 40px;
        height: 25px;
        background: blue;
        position: relative;
        cursor: pointer;
      }
      #path {
        outline: 1px solid #E8C48E;
        width: 540px;
        height: 25px;
      }
    </style>
  </head>
  <body>
    <div id="path">
      <div id="brick"></div>
    </div>
    <script>
      function makeEaseOut(time) {
        return function(fractionOfTime) {
          return 1 - time(1 - fractionOfTime);
        }
      }
      function bounce(fractionOfTime) {
        for(let a = 0, b = 1, result; 1; a += b, b /= 2) {
          if(fractionOfTime >= (7 - 4 * a) / 11) {
            return -Math.pow((11 - 6 * a - 11 * fractionOfTime) / 4, 2) + Math.pow(b, 2)
          }
        }
      }
      let bounceEaseOut = makeEaseOut(bounce);
      brick.onclick = function() {
        animate({
          duration: 2000,
          timing: bounceEaseOut,
          draw: function(progress) {
            brick.style.left = progress * 500 + 'px';
          }
        });
      };
    </script>
  </body>
</html>

In case, there is an animation effect at the start, it will be demonstrated at the end.

In the graph above, red color demonstrates the regular bounce, and blue color- the easOut bounce. Within the regular bounce, the object bounces at the bottom then sharply jumps to the top, at the end. After easeOut, first, it jumps to the top, then bounces there.

EaseInOut

Alos, the effect can be shown both at the start and the end of the animation. Such kind of transform is called “easeInOut”. The calculation of the timing function is shown below:

if (fractionOfTime <= 0.5) { // first half of the animation
  return time(2 * fractionOfTime) / 2;
} else { // second half of the animation
  return (2 - time(2 * (1 - fractionOfTime))) / 2;
}

Here is the wrapper code:

function makeEaseInOut(time) {
  return function (fractionOfTime) {
    if (fractionOfTime < .5)
      return time(2 * fractionOfTime) / 2;
    else
      return (2 - timing(2 * (1 - fractionOfTime))) / 2;
  }
}
bounceEaseInOut = makeEaseInOut(bounce);

The action of bounceEaseInOut is the following:

<!DOCTYPE HTML>
<html>
  <head>
    <title>Title of the Document</title>
    <script src="https://js.cx/libs/animate.js"></script>
    <style>
      #brick {
        width: 50px;
        height: 25px;
        background: blue;
        position: relative;
        cursor: pointer;
      }
      #path {
        outline: 1px solid #E8C48E;
        width: 540px;
        height: 25px;
      }
    </style>
  </head>
  <body>
    <div id="path">
      <div id="brick"></div>
    </div>
    <script>
      function makeEaseInOut(time) {
        return function(fractionOfTime) {
          if(fractionOfTime < .5) return time(2 * fractionOfTime) / 2;
          else return(2 - time(2 * (1 - fractionOfTime))) / 2;
        }
      }
      function bounce(fractionOfTime) {
        for(let a = 0, b = 1, result; 1; a += b, b /= 2) {
          if(fractionOfTime >= (7 - 4 * a) / 11) {
            return -Math.pow((11 - 6 * a - 11 * fractionOfTime) / 4, 2) + Math.pow(b, 2)
          }
        }
      }
      let bounceEaseInOut = makeEaseInOut(bounce);
      brick.onclick = function() {
        animate({
          duration: 2000,
          timing: bounceEaseInOut,
          draw: function(progress) {
            brick.style.left = progress * 500 + 'px';
          }
        });
      };
    </script>
  </body>
</html>

So, two graphs are joined into one by the easeInOut: the regular easeIn for the first half, and the reversed easeOut- for the second part.

So, the first part of the animation is scaled down easeIn, and the second part - easeOut. The animation begins and ends with the same effect.

Summary

The JavaScript animation is a unique means that allow making complex animations. So, the animations that CSS can’t handle, can be dealt with using JavaScript.

As a rule, the JavaScript animations are implemented through requestAnimationFrame. This built-in method helps to set a callback function to run at the moment the browser will be preparing a repaint. Generally, it happens soon, the browser specifies the exact time.




Do you find this helpful?

Related articles