The best way to make animation at 60 frames per second: requestAnimationFrame!
The requestAnimationFrame(animationLoop) is very similar to setTimeout:
It targets 60 frames/s: requestAnimationFrame asks the browser to schedule a call to the animationLoop function passed as parameter in 1/60th of a second (equivalent to 16.6ms). Keep in mind that most monitors cannot display more than 60 frames per second (FPS). Note that whether humans can tell the difference among high frame rates depends on the application; most games are acceptable above 30 FPS, and virtual reality might require 75 FPS to achieve a natural feel. Some gaming monitors go up to 144 FPS (pro players in e-sport train themselves on Counter Strike with a 150 frames/s rate).
It calls the function only ONCE, so if you want a continuous animation, like with setTimeout, you need to call requestAnimationFrame again at the end of the animationLoop function.
It has, however, several advantages over setInterval and setTimeout:
The scheduling is much more accurate: if the code inside the function can be executed in less than 16.6ms, then the average error between the scheduled time and the actual time will be much smaller than with the old functions.
High resolution timer: even if this difference is small, the function that is called after 16.6ms has an extra parameter that is a high resolution time, very useful for writing games that do time-based animation. Time-based animation is studied in detail in the W3Cx HTML5 Apps and Games course. It is a technique that comprises measuring the amount of time elapsed between two frames, then computing the distance in pixels to move objects on screen so that the visible speed for a human eye remains constant, even if the frame rate is not.
Multiple animations are merged: browsers can bundle animations happening at the same time into a single paint redraw (thus happening faster/with less CPU cycles), solving the problems that can occur with simultaneous setInterval calls.
CPU/GPU optimization, battery saved on mobiles: if the JavaScript execution is occurring in a tab/window which is not visible, it doesn’t have to be drawn. However the animation loop is still executed (objects will be moved, not drawn). This is the same when a mobile phone or tablet screen is black or if the application is put in background.
You will note that requestAnimationFrame(function) is used like setTimeout(function, delay). A call to requestAnimationFrame just asks the browser to call the function passed as a parameter ONCE, and the target delay is fixed, and corresponds to a 60 frames/s frame rate (16.6ms). Notice that an id is used for stopping an animation with cancelAnimationFrame(id).
Source code:
<body onload="init();">
<script>
var canvas, ctx;
function init() {
// This function is called after the page is loaded
// 1 - Get the canvas
canvas = document.getElementById('myCanvas');
// 2 - Get the context
ctx=canvas.getContext('2d');
// 3 - start the animation
startAnimation();
}
var id;
function animationLoop(timeStamp) {
// 1 - Clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2 Draw
drawShapes(...);
// 3 Move
moveShapes(...);
// call mainloop again after 16.6ms (corresponds to 60 frames/second)
id = requestAnimationFrame(animationLoop);
}
function startAnimation() {
id = requestAnimationFrame(animationLoop);
}
function stopAnimation() {
if (id) {
cancelAnimationFrame(id);
}
}
</script>
</body>
Check the example below:
Source code extract - please compare with the previous example that used setInterval():
function animationLoop(timeStamp) {
// 1 - Clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2 - Draw
drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow');
// 3 - Move
monsterX += 10;
monsterX %= canvas.width
monsterAngle+= 0.01;
// call mainloop again after 16.6 ms (60 frames/s)
requestId = requestAnimationFrame(animationLoop);
}
function start() {
// Start the animation loop, targets 60 frames/s, this
// calls animationLoop only ONCE!
requestId = requestAnimationFrame(animationLoop);
}
function stop() {
if (requestId) {
cancelAnimationFrame(requestId);
}
}
Notice that calling requestAnimationFrame(animationLoop) at line 19, and after that from within the loop at line 14, asks the browser to call the animationLoop function so that the delta between calls will be as close as possible to 16.6ms (this corresponds to 1/60th of a second).
This target may be hard to reach if:
The animation loop content is too complex,
The target device that runs the animation is a low end phone or an old computer,
The scheduler may be a bit late or a bit in advance (even if this kind of error is much smaller with requestAnimationFrame than with setInterval or setTimeout).
Many HTML5 games perform what we call a "time-based animation". For this, we need an accurate timer that will tell us the elapsed time between each animation frame.
According to this time, we can compute the positional change for each object on the screen, so to retain temporal integrity (for a human eye), independently of the CPU or GPU of the computer or mobile device that is running the game (for example).
The timeStamp parameter of the animationLoop function (line 1 in the above code) is useful for exactly that: it gives a high resolution time. By comparing timestamps between consecutive calls to the animationLoop, we can calculate, to sub-millisecond accuracy, elapsed time between two frames.
Using time-based animation, and more generally, using the canvas element for writing HTML5 games, is part of the W3Cx HTML5 Apps and Games course.
Current support is really good and all modern browsers support this API.