Friday 8 May 2015

Smooth Animations In Your Browser With requestAnimationFrame()

In the last post, we discussed how to bring your game to life by animating your controllable character with sprites.

The key to achieving a smooth animation using this method, is to make sure that we achieve an acceptable frame rate.

In this post, we will talk about frame rendering in the browser, and particular the requestAnimationFrame() API that we have available to us.

The rate at which frames are painted to the screen can make our animations seem very silky smooth as the rate gets higher (to a certain point).

Our computer monitors refresh rate is usually set to 60 herz, so any frame rate above that would be overkill.

To try to achieve a 60fps rate, we must paint a new frame every 16.666 milliseconds (1000ms/60fps).

Before the days of requestAnimationFrame(), we had available to us, two methods of animating in javascript:

setInterval() and setTimeout().

Both functions take a function and a timer as an argument.
The difference between them is that setInterval will call the passed function at the rate your timer specifies.
The setTimeout function, will only call the function once, after the timer passes.

The following block of code shows you how you could use the setTimeout function to draw some animations on a canvas for instance:

1
2
3
4
5
function draw() {
    setTimeout(draw, 16);
    // Drawing code goes here
}
draw(); 

This will, in essence, call the draw function every 16 milliseconds.
This is perfect, everything we need, right?

Wrong...

There is a problem with using this method, as we never take into account what else is going on in the browser while the timer is ticking down.
Firstly, the animation could be behind a hidden browser tab.
In chrome, the function is throttled to 1fps, but you cannot depend on this in other browsers.
The animation could be using some needless resources.
Also, if the animation is scrolled off the page, there is no need to repaint it every time.

It all seems like a bit of a waste...

Furthermore, the browser has to juggle the painting of your animation along with its own re-rendering of the browser window, whenever anything else happens, like scrolling or clicking for example.

It would be nice if we could paint everything in sync, and use up a lot less resources.

requestAnimationFrame() to the rescue!

We can use this API to ask our browser to call a certain function when it is ready to repaint the screen, which is usually at 60fps.

The browser will then decide best when to call the function, and it will take into account all the issues we just described above, giving you a smooth 60fps animation without using up as many resources as we used when calling setInterval().

The following block of code illustrates how we could utilize this API to smoothly animate your canvas:

1
2
3
4
5
function draw() {
    requestAnimationFrame(draw);
    // Drawing code goes here
}
draw();

It is very similar to the code we had before, except now we use the new function and we do not specify a frame rate.

If you want to control the frame rate yourself, you could simply wrap the call to requestAnimationFrame() in a setTimeout() block like so:

1
2
3
4
5
6
7
var fps = 15;
function draw() {
    setTimeout(function() {
        requestAnimationFrame(draw);
        // Drawing code goes here
    }, 1000 / fps);
}

Browser compatibility has not been perfect, so you can find a shim here to allow you to hide the nasty details of accessing the API across browsers:

CanvasQuery uses requestAnimationFrame() under the hood in your game loop, so you do not have to worry about it.

However, it is useful to know what is going on if you really want to get a handle on your animations!

Until next time...