Simple Animations Using requestAnimationFrame

    Dmitri Lau
    Share

    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