In this post, i'll try to explain a few basic techniques to animate 'things' in the canvas, to make them live and bounce.

Step one : Keep drawing

To animate, one need to draw a succession of different images in time. In javascript, yes, time can be handled with setTimeout or setInterval. In oudated post, you'll find code using either method to animate. But not only they lack accuracy and have a high overhead, but they're just not relevant.

The way to handle time properly to animate is to use requestAnimationFrame. Despite its complicated name, its meaning is most simple :

  requestAnimationFrame(myDrawingMethod);

means :

"Whenever you are ready to draw on screen, please call myDrawingMethod "

Notice that this will only call the method once, so to animate you need to call again and again requestAnimationFrame. The simplified pattern is this one :

   function animate() {
    requestAnimationFrame(animate); // call again next time you're ready to draw, please.
    // update everything (move, collision, spawn)
    myUpdateMethod();
    // draw
    myDrawingMethod(); // actually draw things 
 }

 animate();

!!! Beware of outdated -or just plain stupid- articles that suggests to use setTimeout or setInterval to limit the frame rate that requestAnimationFrame provides. You might just as well look at the sun to precisely set your watch. !!!

Now that might seem ok but if you draw without erasing first, drawings will, like, 'pile up', and that's not what you want. Another thing is that requestAnimationFrame not only calls you at the right time, but also tells you what time is it : let's take advantage of this to measure time for free.

   var applicationTime = 0; // time elapsed in the application
 //
 var _lastRAF = 0;

 function animate(currentTime) {
    // time handling
    var dt = currentTime - lastRAF ; // time elapsed since last frame
    // call again please
    requestAnimationFrame(animate); 
    // erase
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
    // draw
    myDrawingMethod(); // actually draw things 
    // update everything (move, collision, spawn)
    myUpdateMethod(dt);
    // update time
    applicationTime += dt;
    _lastRAF = currentTime;
 }

 function launchAnimation() {
      requestAnimationFrame(_launchAnimationNow);
      function _launchAnimationNow(currentTime) {
          _lastRAF = currentTime;
          requestAnimationFrame(animate);
      }
 }

 launchAnimation();

You might want to take a little time understanding the code above : to simplify the launch of the animation, we just provide a simple launchAnimation function taking no arguments. In this function we retrieve the current time by using rAF, to initialize _lastRAF. Every time the animate function gets called, we measure how much time elapsed and we will then increase the application time by that amount : this way we have a 'local clock' that we can use. In Chrome, this clock is precise to the microsecond, way better than Date.now() and its milliseconds. Firefox might also come to that precision in the future.

Notice also an imprtant change : we inform now the update method of the 'dt', the delta time / the time elapsed between two draws. By taking into account this dt in the update function, you ensure your app will behave the same on a 20Hz mobile or on a 150Hz mad geek computer. So never write x += 10, for instance to move something to the right every call. If you are happy with this value on your 60Hz (=16.6ms) screen, just write x+=10*dt/16.6 and your code will behave just the same on every screen.

Things gets complicated.

I hope you won't get vexed, but :

  • your user might tab-out and leave your application alone for some time.
  • your computation and/or your drawings might take a loooooong time. In both cases, the issue will be that rAF won't be called for a long time, and so your application time (your 'clock') and your dt will be huge and won't mean anything. You have to cap/limit this dt to avoid your animation to jump by a huge amount when your user gets back to the tab where your application stands. After all, that's what the user expects : when he/she comes back, he expect that the application paused and to be just one frame 'later'.
    So we have to limit dt :

     if (dt>200) dt = 16; // if too much time elapsed, consider just one frame elapsed.
    


Even worse

But things are even more complicated : the canvas, while optimized, is still quite greedy, and people owning a 150Hz or more screen (i once was... sorry) won't be that happy to have the computer turning into a hoven if you animate at full speed. So you have to fix also a lower bound for the time elapsed.

While we're at it, let's add a feature that we have for free : if you have your animation time-driven by a delta-time, what about using a speed parameter to mutiply that dt to speed up / slow down the animation ???

   var timeSpeed = 1;       // < 1 slow down  >1 speed up
 var applicationTime = 0; // time elapsed in the application
 var maxFrameTime = 200;
 var minFrameTime = 12;
 //
 var _lastRAF = 0;

 function animate(currentTime) {
    // time handling
    var dt = currentTime - lastRAF ; // time elapsed since last frame
    // call again please
    requestAnimationFrame(animate); 
    // cap dt
    if (dt<minFrameTime) return; // skip if not enough time elapsed
    if (dt>maxFrameTime) dt = 16; // consider just one frame elapsed if too much time elapsed.
    dt *= timeSpeed;
    // erase
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
    // draw        
    myDrawingMethod(); // actually draw things 
    // update everything (move, collision, spawn)
    myUpdateMethod(dt);
    // update time
    applicationTime += dt;
    _lastRAF = currentTime;
 }

 function launchAnimation() {
      requestAnimationFrame(_launchAnimationNow);
      function _launchAnimationNow(currentTime) {
          _lastRAF = currentTime;
          requestAnimationFrame(animate);
      }
 }

 launchAnimation();

Notice that i splitted things into update and draw. Drawing should just ... well... draw, so that if for instance you pause your game you just stop updating it, and you can still draw it, maybe drawing some things on top.

The pause example will write (everything else is the same):

      //
    if (!paused) myUpdateMethod(dt);   // move things by dt 
    //
    myDrawingMethod();  // actually draw things 
    if (paused) {
       // draw a text like 'PAUSE' or like
    }

Have a look at my basic animation codepen i wrote here : http://codepen.io/gamealchemist/pen/VeawyL

Happy coding !!!


1,147 0 0