Measuring time between frames

Measuring time between frames to achieve a constant speed on screen, even when the frame rate changes

Method 1 – Using the JavaScript Date object

Let’s modify the example from the previous lesson slightly by adding a time-based animation.  Here we use the “standard JavaScript” way for measuring time, using JavaScript’s Date object:

  1. var time = new Date().getTime();

The getTime() method returns the number of milliseconds since midnight on January 1, 1970. This is the number of milliseconds that have elapsed during the Unix epoch (!).

There is an alternative. We could have called:

  1. var time = Date.now();

So, if we measure the time at the beginning of each animation loop, and store it, we can then compute the delta of times elapsed between two consecutive loops.

We then apply some simple maths to compute the number of pixels we need to move the shape to achieve a given speed (in pixels/s).

First example that uses time based animation: the bouncing square

Online example at JSBin:

Bouncing square with time bases animation

Source code from the example:

  1. <!DOCTYPE html>
  2. <html lang=“en”>
  3. <head>
  4.   <meta charset=utf-8 />
  5.   <title>Move rectangle using time based animation</title>
  6.   
  7.     var canvas, ctx;
  8.     var width, height;
  9.     var x, y, incX; // incX is the distance from the previously drawn
  10.                     // rectangle to the new one
  11.     var speedX; // speedX is the target speed of the rectangle, in pixels/s
  12.     // for time based animation
  13.     var now, delta;
  14.     var then = new Date().getTime();
  15.     // Called after the DOM is ready (page loaded)
  16.     function init() {
  17.       // Init the different variables
  18.       canvas = document.querySelector(“#mycanvas”);
  19.       ctx = canvas.getContext(‘2d’);
  20.       width = canvas.width;
  21.       height = canvas.height;
  22.       x=10; y = 10;
  23.       // Target speed in pixels/second, try with high values, 1000, 2000…
  24.       speedX = 200;
  25.       // Start animation
  26.      animationLoop();
  27.    }
  28.    function animationLoop() {
  29.      // Measure time
  30.      now = new Date().getTime();
  31.      // How long between the current frame and the previous one?
  32.      delta = now  then;
  33.      //console.log(delta);
  34.      // Compute the displacement in x (in pixels) in function of the time elapsed and
  35.      // in function of the wanted speed
  36.      incX = calcDistanceToMove(delta, speedX);
  37.      // an animation involves: 1) clear canvas and 2) draw shapes,
  38.      // 3) move shapes, 4) recall the loop with requestAnimationFrame
  39.      // clear canvas
  40.      ctx.clearRect(0, 0, width, height);
  41.      ctx.strokeRect(x, y, 10, 10);
  42.      // move rectangle
  43.      x += incX;
  44.      // check collision on left or right
  45.      if((x+10 >= width) || ( 0)) {
  46.         // cancel move + inverse speed
  47.         x -= incX;
  48.         speedX = speedX;
  49.      }
  50.      // Store time
  51.      then = now;
  52.      requestAnimationFrame(animationLoop);
  53.  }
  54.  // We want the rectangle to move at a speed given in pixels/second
  55.  // (there are 60 frames in a second)
  56.  // If we are really running at 60 frames/s, the delay between
  57.  // frames should be 1/60
  58.  // = 16.66 ms, so the number of pixels to move = (speed * del)/1000.
  59.  // If the delay is twice as
  60.  // long, the formula works: let’s move the rectangle for twice as long!
  61.  var calcDistanceToMove = function(delta, speed) {
  62.      return (speed * delta) / 1000;
  63.  }
  64.  
  65. </head>
  66. <body onload=init();>
  67.  <canvas id=“mycanvas” width=“200” height=“50” style=border: 2px solid black></canvas>
  68. </body>
  69. </html>

In this example, we only added a few lines of code for measuring the time and computing the time elapsed between two consecutive frames (see line 38). Normally, requestAnimationFrame(callback) tries to call the callback function every 16.66 ms (this corresponds to 60 frames/s)… but this is never exactly the case. If you do a console.log(delta)in the animation loop, you will see that even on a very powerful computer, the delta is “very close” to 16.6666 ms, but 99% of the time it will be slightly different.

The function calcDistanceToMove(delta, speed) takes two parameters: 1) the time elapsed in ms, and 2) the target speed in pixels/s. 

Try this example on a smartphone, use this link: https://jsbin.com/jeribi to run the JSBin example in stand-alone mode. Normally you should see no difference in speed, but it may look a bit jerky on a low-end smartphone or on a slow computer. This is the correct behavior.

Or you can try the next example that simulates a complex animation loop that takes a long time to draw each frame…

A simulation that spends a lot of time in the animation loop, to compare with the previous example

Try it on JsBin:

We added a long loop in the middle of the animation loop. This time, the animation should be very jerky. However, notice that the apparent speed of the square is the same as in the previous example: the animation adapts itself!

  1. function animationLoop() {
  2.    // Measure time
  3.   now = new Date().getTime();
  4.   // How long between the current frame and the previous one ?
  5.   delta = now  then;
  6.   //console.log(delta);
  7.   // Compute the displacement in x (in pixels) in function of the time elapsed and
  8.   // in function of the wanted speed
  9.   incX = calcDistanceToMove(delta, speedX);
  10.   // an animation is : 1) clear canvas and 2) draw shapes,
  11.   // 3) move shapes, 4) recall the loop with requestAnimationFrame
  12.   // clear canvas
  13.   ctx.clearRect(0, 0, width, height);
  14.   for(var i = 0; i < 50000000; i++) {
  15.     // just to slow down the animation
  16.   }
  17.   ctx.strokeRect(x, y, 10, 10);
  18.   // move rectangle
  19.   x += incX;
  20.   // check collision on left or right
  21.   if((x+10 >= width) || (x <= 0)) {
  22.    // cancel move + inverse speed
  23.    x -= incX;
  24.    speedX = speedX;
  25.   }
  26.   // Store time
  27.   then = now;
  28.   requestAnimationFrame(animationLoop);
  29. }

Method 2 –  USING THE NEW HTML5 HIGH-RESOLUTION TIMER

Since the beginning of HTML5, game developers, musicians, and others have asked for a sub-millisecond timer to be able to avoid some glitches that occur with the regular JavaScript timer. This API is called the “High Resolution Time API”.

This API is very simple to use – just do:

  1. var time = performance.now();

… to get a sub-millisecond time-stamp. It is similar to Date.now() except that the accuracy is much higher and that the result is not exactly the same. The value returned is a floating point number, not an integer value!

From this article that explains the High Resolution Time API:  “The only method exposed is now(), which returns a DOMHighResTimeStamp representing the current time in milliseconds. The timestamp is very accurate, with precision to a thousandth of a millisecond. Please note that while Date.now() returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC, performance.now() returns the number of milliseconds, with microseconds in the fractional part, from performance.timing.navigationStart(), the start of navigation of the document, to the performance.now() call. Another important difference between Date.now() and performance.now() is that the latter is monotonically increasing, so the difference between two calls will never be negative.

To sum up:

    • performance.now() returns the time since the load of the document (it is called a DOMHighResTimeStamp), with a sub mill-secondaccuracy, as a floating point value, with very high accuracy.
    • Date.now() returns the number of mill-seconds since the Unix epoch, as an integer value.

Support for this API is good as of October 2017 (check an up to date version) : 

High resolution time API support (caniuse.com table)

Here is a version on JSBin of the previous example with the bouncing rectangle, that uses the high resolution timer.

Source code of the example:

  1.  
  2.  
  3.    
  4.    var speedX; // speedX is the target speed of the rectangle in pixels/s
  5.    // for time based animation
  6.    var now, delta;
  7.    // High resolution timer
  8.    var then = performance.now();
  9.    // Called after the DOM is ready (page loaded)
  10.    function init() {
  11.      
  12.    }
  13.    function animationLoop() {
  14.      // Measure time, with high resolution timer
  15.      now = performance.now();
  16.      // How long between the current frame and the previous one?
  17.     delta = now  then;
  18.     //console.log(delta);
  19.     // Compute the displacement in x (in pixels) in function
  20.     // of the time elapsed and
  21.     // in function of the wanted speed
  22.     incX = calcDistanceToMove(delta, speedX);
  23.     //console.log(“dist = ” + incX);
  24.     // an animation involves: 1) clear canvas and 2) draw shapes,
  25.     // 3) move shapes, 4) recall the loop with requestAnimationFrame
  26.     // clear canvas
  27.    ctx.clearRect(0, 0, width, height);
  28.    ctx.strokeRect(x, y, 10, 10);
  29.    // move rectangle
  30.    x += incX;
  31.    // check collision on left or right
  32.    if((x+10 >= width) || ( 0)) {
  33.       // cancel move + inverse speed
  34.       x -= incX;
  35.       speedX = speedX;
  36.    }
  37.    // Store time
  38.    then = now;
  39.    // call the animation loop again
  40.    requestAnimationFrame(animationLoop);
  41.  }
  42.  
  43.  script>

Only two lines have changed but the accuracy is much higher, if you uncomment the console.log(…) calls in the main loop. You will see the difference.

Method 3 – using the optional timestamp parameter of the callback function of requestAnimationFrame

This is the recommended method!

There is an optional parameter that is passed to the callback function called by requestAnimationFrame: a timestamp!

The requestAnimationFrame API specification  says that this timestamp corresponds to the time elapsed since the page has been loaded.

It is similar to the value sent by the high resolution timer using performance.now().

Here is a running example  of the animated rectangle, that uses this timestamp parameter.

Online example at JSBin:

Source code of the example:

  1.  lang=“en”>
  2.  charset=utf-8 />
  3. Time based animation using the parameter of the requestAnimationFrame callback
  4.  
  5.    var canvas, ctx;
  6.    var width, height;
  7.    var x, y, incX; // incX is the distance from the previously drawn rectangle
  8.                    // to the new one
  9.    var speedX;     // speedX is the target speed of the rectangle in pixels/s
  10.    // for time based animation
  11.    var now, delta=0;
  12.    // High resolution timer
  13.    var oldTime = 0;
  14.    // Called after the DOM is ready (page loaded)
  15.    function init() {
  16.      // init the different variables
  17.      canvas = document.querySelector(“#mycanvas”);
  18.      ctx = canvas.getContext(‘2d’);
  19.      width = canvas.width;
  20.      height = canvas.height;
  21.      x=10; y = 10;
  22.      // Target speed in pixels/second, try with high values, 1000, 2000…
  23.      speedX = 200;
  24.      // Start animation
  25.      requestAnimationFrame(animationLoop);
  26.    }
  27.    function animationLoop(currentTime) {
  28.      // How long between the current frame and the previous one?
  29.      delta = currentTime  oldTime;
  30.      // Compute the displacement in x (in pixels) in function of the time elapsed and
  31.      // in function of the wanted speed
  32.      incX = calcDistanceToMove(delta, speedX);
  33.      // clear canvas
  34.      ctx.clearRect(0, 0, width, height);
  35.      ctx.strokeRect(x, y, 10, 10);
  36.      // move rectangle
  37.      x += incX;
  38.      // check collision on left or right
  39.      if(((x+10) > width) || ( 0)) {
  40.        // inverse speed
  41.        x -= incX;
  42.        speedX = speedX;
  43.      }
  44.      // Store time
  45.      oldTime = currentTime;
  46.      // asks for next frame
  47.      requestAnimationFrame(animationLoop);
  48.    }
  49.    var calcDistanceToMove = function(delta, speed) {
  50.      return (speed * delta) / 1000;
  51.    }
  52.  
  53. </head>
  54. <body onload=init();>
  55.  <canvas id=“mycanvas” width=“200” height=“50” style=border: 2px solid black></canvas>
  56. </body>
  57. </html>


Leave a comment