Making HTML5 Canvas Canvas

Sunday, September 15, 2013

Appendix 2: RequestAnimationFrame

In the past, for making timers in general, the function setTimeout was used. This function has been great since long ago for every kind of timed actions on web sites, nevertheless, it wasn't made having animations on mind, which require multiple calls per second to paint on screen, taking many resources from our PC, even when we are away from the application.

The browser developer companies have been concious of that, so they came with a better solution for this task: the requestAnimationFrame function.

This function optimizes the use of information, updating itself automatically as soon as the CPU allows it (Usually 60 frames per second on desktops), improving the capacity of information management on animations, consuming less resources, and even putting into sleep the cycle when the application is no longer on focus, giving as a result, a better handling on animations.

To use requestAnimationFrame, you just need to call it as the first line on a function, sending as first parameter the same function that called it, so it calls back to it after the interval time, as is shown next:
function run(){
    requestAnimationFrame(run);
    act();
    paint(ctx);
}
requestAnimationFrame(run) equals as calling a setTimeout(run,17), but in an optimized way.

Support for old browsers.


At the time this was written, requestAnimationFrame is a relatively new function, so browsers that are not up to date might not support it, or use an experimental non-standard function of it. To know more about the version when this function was implemented, you can visit http://caniuse.com/requestanimationframe, where it is shown the advances on it's support.

To be able to use requestAnimationFrame on these old browsers, there are many possible solutions. The most simple and popular, is adding this function to your code:
window.requestAnimationFrame=(function(){
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        function(callback){window.setTimeout(callback,17);};
})();
This custom function will create a requestAnimationFrame with the best possible alternative. First it will try to use the standard version. If it fails, it will try to load the webkit version, supported by older versions of Safari and Google Chrome, as well as many mobile browsers. If it fails, it will try to load the mozilla version, which is the version older Firefox browsers supported. Finally, if there is no support for any version, standard or experimental, as the case of Opera with Presto and older versions of Internet Explorer, it will fall back to a classic setTimeout function at 17 milliseconds, which is more less the update rate of requestAnimationFrame.

This way, we will be able to use requestAnimationFrame for the development of our games, even on browsers out of date.

Measuring the frames per second.


For testing the true performance of requestAnimationFrame on our devices, we are going to get the frames per second (FPS) of our canvas. To do this, we will use the code at  Part 2. Animating the canvas. We shall start creating four variables, whose purposes I'll explain as we go through the explanation:
var time=0,FPS=0,frames=0,acumDelta=0;
Insert the next code inside the run function, just after calling the requestAnimationFrame. We will begin by getting the delta time, this is, the time since the last time was executed, in milliseconds:
    var now=Date.now();
    var deltaTime=now-time;
    if(deltaTime>1000)deltaTime=0;
    time=now;
At the first line, we save the value of the Date.now(); to a variable. This value is the time in milliseconds since January 1, 1970, 00:00:00 UTC.

At the second line, we get the delta time by subtracting the now, to the time we had saved. To better understand this, we must notice that on the fourth line, the time variable gets the value of now. Now, we can understand that at the next cycle, now-time will give us the delta time.

Nevertheless, at the first cycle, time has a value of 0, therefore now-time would give us an enormous value that would cause undesired effects. Because of that, at the third line, if the delta time is over 1000 (a second), it's value is discarded. This also works to prevent undesired values in case the user goes out the current tab for a moment, or the PC hangs for a few seconds.

After understanding this complex but simple section, we continue with the next part. At every cycle we will add in one the frames, and add the delta time to acumDelta:
    frames++;
    acumDelta+=deltaTime;
After that, we will find out if a second has gone trough, asking if acumDelta is bigger than 1000; if this is the case, we will assign the frames to our FPS variable, and reset the frames and acumDelta variables:
    if(acumDelta>1000){
        FPS=frames;
        frames=0;
        acumDelta-=1000;
    }
This way, we get the frames per second on our game. Now we only need to print them on screen:
    ctx.fillText('FPS: '+FPS,10,10);
The possible disadvantage of using requestAnimationFrame, is that the update rate is very slow, making some of our games slow on low performance devices like older computers and mobile devices. To prevent this, a method to regulate the time is needed, from which there are many. We will learn some of the most popular at the next part.

Final Code:


[Canvas not supported by your browser]

'use strict';
window.addEventListener('load',init,false);
var canvas=null,ctx=null;
var time=0,FPS=0,frames=0,acumDelta=0;
var x=50,y=50;

function init(){
    canvas=document.getElementById('canvas');
    canvas.style.background='#000';
    ctx=canvas.getContext('2d');
    run();
}

function run(){
    requestAnimationFrame(run);
    
    var now=Date.now();
    var deltaTime=now-time;
    if(deltaTime>1000)deltaTime=0;
    time=now;
    
    frames++;
    acumDelta+=deltaTime;
    if(acumDelta>1000){
        FPS=frames;
        frames=0;
        acumDelta-=1000;
    }

    act();
    paint(ctx);
}

function act(){
    x+=2;
    if(x>canvas.width)
        x=0;
}

function paint(ctx){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle='#0f0';
    ctx.fillRect(x,y,10,10);

    ctx.fillStyle='#fff';
    ctx.fillText('FPS: '+FPS,10,10);
}

window.requestAnimationFrame=(function(){
    return window.requestAnimationFrame || 
        window.webkitRequestAnimationFrame || 
        window.mozRequestAnimationFrame || 
        function(callback){window.setTimeout(callback,17);};
})();

No comments:

Post a Comment