HTML5 Game Programming Gems – requestAnimationFrame

Whether talking about character movement or bouncing balls, sprite animation is a fundamental aspect of game development. There are a million ways to skin the cat with this but it all boils down to iterating through animation frames from within a loop.

A simple javascript method for initiating an animation loop is to call setInterval to create a running timer and execute animation code every iteration through the timer function. A simple example to demonstrate this uses an interval of 33ms which roughly equates to 30 frames per second :

1
2
3
4
5
setInterval(function() {
   for(var a in animations){
      animations[a].update();
   }
}, 33);

However, this isn’t ideal for a number of reasons:

  • It’s just a setInterval – not optimized for animation
  • All animations would run regardless of whether they are currently visible – power hungry
  • Loop timing would be anything but consistent

Despite the drawbacks, this method of performing javascript based animation has been the de facto standard… until HTML5 came along and the thought crossed someone’s mind that since so many developers do this, let’s get the browser to do it for them. This epic thinking would bring with it the possibility of lower level optimization, more consistent timing and less power draining animations.

So along came the function, requestAnimationFrame, which is more or less implemented in all modern browsers, although not really. In fact, like many of the new items in the HTML5 standard, requestAnimationFrame is available in browser specific incarnations, each browser using its own naming conventions. As such, we need to use the ubiquitous double pipe OR logical operator to setup requestAnimationFrame, falling back to setInterval for browsers that don’t support it (yet):

Paul Irish has a complete polyfill by Erik Müller for nicely setting up a cross-browser window.requestAnimationFrame on github:gist: https://gist.github.com/1579671

Theoretically, requestAnimationFrame could then be subbed into our original loop, replacing setInterval:

1
2
3
4
5
6
7
var animate = function(timestamp){
   for(var a in animations){
      animations[a].update(timestamp);
   }
   window.requestAnimationFrame(animate);
}
window.requestAnimationFrame(animate);

All good…. But here’s a more complete version of this animation loop, allowing for per-animation intervals.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var animations = new Array();
function addAnimation(_id, _interval, _target, stepFunction){
   var data = {id:_id, 
            interval:_interval,
            target:_target,
            lastTime:Date.now(),
            delta:0,
            on:false,
            start:function(){
               this.on = true;
            },
            stop:function(){
               this.on = false;
            },
            animationStepFunction:stepFunction};
   animations[_id] = data;
   return data;
}
function iterateAnimations(timestamp){
   for(var a in animations){
      if(animations[a].on){
         animations[a].delta += (timestamp - animations[a].lastTime);
         animations[a].lastTime = timestamp;
         if(animations[a].delta >= animations[a].interval){
            animations[a].animationStepFunction(animations[a]);
            animations[a].delta = 0;
         }
      }
   }
   window.requestAnimationFrame(iterateAnimations);
}
window.requestAnimationFrame(iterateAnimations);

The above code has also been modified to incorporate some “short-cuts” for element CSS transforms (not shown above). With this stuff, you can produce some really nice (and smooth) animations. For example, behold the magic below which looks great, even on my BlackBerry 9900 ;)

The code for this in a nice little bundle of 24 lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
   <div id="container" style="width:100%;height:100%;"></div>
   <script type="text/javascript" src="../GameUtils.js"></script> 
   <script type="text/javascript">
      for(var i=0;i<50;i++){
         (function(_i){
            var d = document.createElement("div");
            d.setAttribute("id", "ball" + _i);
            d.setAttribute("style", "width:15px;height:15px;background-color:red;border-radius:50%;position:absolute;");
            $("container").appendChild(d);
            addAnimation("ballAnimation"+_i, 33, $("ball"+_i), function(data){
               var b = data.target;                
               var c = data.counter = (data.counter!==undefined)?data.counter+0.05:_i*2;
               var w = Math.floor(b.parentNode.clientWidth/2);
               var h = Math.floor(b.parentNode.clientHeight/2);
               x = Math.min(Math.max(0, w + Math.floor(w*Math.sin(c))),w*2-b.clientWidth);
               y = Math.max(0, h + Math.floor(h*Math.sin(c/1.2)));
               b.transform('translate('+x+'px,'+y+'px) translateZ(0) scale('+(Math.max(0.5,2+Math.cos(c)))+')');
            }).start();
         }(i));
      }
   </script>
</html>
You can leave a response, or trackback from your own site.
  • http://twitter.com/logicking_com Yuri Dobronravin

    Excellent stuff! Gonna try it in our Logicking HTML5 game engine. It runs completely on DOM so it might increase smoothness and performance.