Simple Animations Using requestAnimationFrame

Share this article

Animating DOM elements involves modifying a CSS style every few milliseconds to give the illusion of movement. This means passing in a callback function to setTimeout and modifying the node’s style object within that callback. Then calling setTimeout again to queue the next animation frame. From the ashes of the phoenix rises a new helper function to write animations called requestAnimationFrame. It started off in Firefox 4 and is slowly being adopted by all browsers including IE 10. And luckily it is easy to make it backwards compatible with older browsers.

window.requestAnimationFrame(callbackFunction);
Unlike setTimeout, which runs after a specified time delay, requestAnimationFrame runs a callback the next time the browser is going to paint the screen. This allows you to synchronize with the paint cycles of the browser, so that you won’t be painting too often or not often enough, which means your animations will be silky smooth, yet not too demanding on your CPU.

Sifting through browser inconsistencies

Currently every browser has a prefixed version of requestAnimationFrame so lets feature detect which version is supported and make a reference to it:
var _requestAnimationFrame = function(win, t) {
  return win["webkitR" + t] || win["r" + t] || win["mozR" + t]
          || win["msR" + t] || function(fn) { setTimeout(fn, 60) }
}(window, "equestAnimationFrame");
Notice how we’re using the bracket notation to access a property on the window object. We’re using the bracket notation because we’re building the property name on the fly using string concatenation. And if the browser doesn’t support it, we’re falling back to a regular function that calls setTimeout after 60 milliseconds to achieve a similar effect.

Building the shell

Now let’s build a simple function that will call our _requestAnimationFrame repeatedly to mimic the animation. To achieve the animation, we’ll need an outer function that serves as an entry point and an inner function that will be called repeatedly, called a stepping function.
function animate() {
  var step = function() {

    _requestAnimationFrame(step);
  }
  step();
}
At every call of the stepping function, we need to keep track of the progress of the animation to know when to end. We’ll calculate when the animation is supposed to finish and base our progress on how much time is left during each cycle.
function animate() {

  var duration = 1000*3,  //3 seconds
      end = +new Date() + duration;

  var step = function() {

    var current = +new Date(),
        remaining = end - current;

    if(remaining < 60) {
      //end animation here as there's less than 60 milliseconds left
      return;

    } else {
      var rate = 1 - remaining/duration;
      //do some animation
    }

    _requestAnimationFrame(step);
  }
  step();
}
Notice we’re doing +new Date() to get the current time in milliseconds. The plus sign coerces the date object into a numerical data type. The rate variable is a number between 0 and 1 that represents the progress rate of the animation.

Making it useful

Now we need to think about the function’s inputs and outputs. Let’s allow the function to accept a function and duration as parameters.
function animate(item) {

  var duration = 1000*item.time,
      end = +new Date() + duration;

  var step = function() {

    var current = +new Date(),
        remaining = end - current;

    if(remaining < 60) {
      item.run(1);  //1 = progress is at 100%
      return;

    } else {
      var rate = 1 - remaining/duration;
      item.run(rate);
    }

    _requestAnimationFrame(step);
  }
  step();
}
And we can call this function like this:
animate({
  time: 3,  //time in seconds
  run: function(rate) { /* do something with rate */ }
});
Inside the run function I’ll put some code that animates the width of a node from “100px” to “300px”.
animate({
  time: 3,
  run: function(rate) {
    document.getElementById("box").style
      .width = (rate*(300 - 100) + 100) + "px";
  }
});

Improving the use-case

It works fine, but what I really want is to be able to input an array of functions that gets called one after the other. So that after the first animation ends, the second animation picks up. We’ll treat the array as a stack, popping off items one at a time. Let’s change the inputs:
function animate(list) {

  var item,
      duration,
      end = 0;

  var step = function() {

    var current = +new Date(),
        remaining = end - current;

    if(remaining < 60) {

      if(item) item.run(1);  //1 = progress is at 100%

      item = list.shift();  //get the next item

      if(item) {
        duration = item.time*1000;
        end = current + duration;
        item.run(0);  //0 = progress is at 0%
      } else {
        return;
      }

    } else {
      var rate = remaining/duration;
      rate = 1 - Math.pow(rate, 3);  //easing formula
      item.run(rate);
    }

    _requestAnimationFrame(step);
  };
  step();
}
When the animation is first run, item is null and remaining is less than 60 milliseconds, so we pop the first item off the array and start executing it. On the last frame of the animation, remaining is also less than 60, so we finish off the current animation and pop the next item off the array and start animating the next item. Notice also that I’ve put the rate
value through an easing formula. The value from 0 to 1 now grows with cubic proportions and makes it look less robotic. To call the animation function we do:
animate([
  {
    time: 2,
    run: function(rate) {
      document.getElementById("box").style
        .width = (rate*(300 - 100) + 100) + "px";
    }
  }, {
    time: 2,
    run: function(rate) {
      document.getElementById("box").style
        .height = (rate*(300 - 100) + 100) + "px";
    }
  }
]);
Notice how the width of the box expands first taking up 2 seconds, before the height expands which takes up another 2 seconds.

Wrapping it up

Let’s clean up our code a little. Notice how we’re calling getElementById so many times that it’s not funny anymore? Let’s cache that and let’s cache the start and end values while we’re at it.
animate([
  {
    time: 2,
    node: document.getElementById("box"),
    start: 100,
    end: 300,
    run: function(rate) {
      this.node.style
        .width = (rate*(this.end - this.start) + this.start) + "px";
    }
  }
]);
Notice how we don’t need to modify the main function, because the run function was part of a self-contained object the whole time and has access to all the properties of the object via the this variable. Now whenever the stepping function is run, we have all variables cached up. And there you have it. A simple animation helper that takes advantage of requestAnimationFrame with a fallback for old browsers. script demo

Frequently Asked Questions (FAQs) about Using requestAnimationFrame for Simple Animations

What is the difference between using requestAnimationFrame and setTimeout/setInterval for animations?

The requestAnimationFrame method provides a smoother and more efficient way for creating animations compared to setTimeout or setInterval. It works by telling the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. This method can be up to 60 times per second under ideal conditions. On the other hand, setTimeout and setInterval functions do not guarantee the smooth execution of animations as they run independently of the browser’s refresh rate.

Can I use requestAnimationFrame in all browsers?

The requestAnimationFrame method is supported in all modern browsers including Chrome, Firefox, Safari, Opera, and Internet Explorer 9 and above. However, for older browsers that do not support this method, you can use a polyfill to provide a fallback.

How can I stop an animation created with requestAnimationFrame?

To stop an animation, you can use the cancelAnimationFrame method. This method cancels a scheduled animation frame request. You just need to pass the ID value returned by the requestAnimationFrame method to cancelAnimationFrame.

Why is my animation not smooth when using requestAnimationFrame?

If your animation is not smooth, it could be due to several reasons. One common reason is that your animation function is doing too much work. Remember, the browser tries to run requestAnimationFrame around 60 times per second. If your function takes too long to execute, it can cause jank or stuttering in the animation.

Can I control the speed of my animation with requestAnimationFrame?

Yes, you can control the speed of your animation by using a time delta technique. This involves calculating the difference in time between the current frame and the last frame, and then using this value to adjust the movement of your animation.

How does requestAnimationFrame handle tab visibility?

One of the advantages of requestAnimationFrame over traditional JavaScript timing functions is that it automatically pauses animations when the tab or window is not visible. This can help to reduce CPU usage and save battery life on mobile devices.

Can I use requestAnimationFrame for animations other than CSS properties?

Yes, while requestAnimationFrame is often used for animating CSS properties, it can also be used for other types of animations. For example, you can use it for canvas animations, SVG animations, and even for animations based on scrolling or user interaction.

How can I chain animations using requestAnimationFrame?

To chain animations, you can call requestAnimationFrame again at the end of your animation function. This will create a loop, allowing you to chain multiple animations together.

Can I use requestAnimationFrame with React or other JavaScript frameworks?

Yes, you can use requestAnimationFrame with any JavaScript framework or library, including React, Angular, Vue.js, and others. You can use it in the same way as you would in plain JavaScript.

Is requestAnimationFrame better than CSS animations or transitions?

It depends on the situation. For simple animations, CSS animations or transitions can be easier and more efficient. However, for complex or highly interactive animations, requestAnimationFrame can provide more control and flexibility.

Dmitri LauDmitri Lau
View Author

Dmitri Lau is a freelance Ajax developer with a penchant for statistical analysis. When not improving a meta template engine's relative response yield, yawning uninterruptedly every night has become a norm which he hopes will soon be over when his Hong Kong based startup picks up.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week