requestAnimationFrame
Creating an animation in JavaScript is one of the simplest things you can do. If you’ve ever tried to do it, you’ll most likely have used either the setTimeout
or setInterval
functions.
Here is a typical example:
function draw() {
// Drawing code goes here
}
setInterval(draw, 100);
This piece of code will call the draw
function once every 100ms forever and ever, until at some point later the clearInterval
function is called. An alternative to this code is to use a setTimeout
function instead, this time inside the draw
function:
function draw() {
setTimeout(draw, 100);
// Drawing code goes here
}
draw();
The single call to the draw
function kicks off the animation loop, and from then on it will call itself repeatedly every 100ms.
Frame rate and setInterval
The smoothness of your animation depends on the frame rate of your animation. Frame rate is measured in frames per second (fps). Film usually runs at 24fps, video at 30fps. The higher this number is, the smoother your animation will look…to a point. More frames, means more processing, which can often cause stuttering and skipping. This is what is meant by the term dropping frames. Because most screens have a refresh rate of 60Hz, the fastest frame rate you should aim for is 60fps. Time for some math!
/**
* 1s = 1000ms (remember that setInterval and setTimeout run on milliseconds)
* 1000ms / 60(fps) = 16.7ms (we'll round this to 17)
*/
// Lights, camera…function!
setInterval(function() {
animateEverything();
}, 17);
What’s wrong with setTimeout and setInterval?
OK, you’ve done this a million times before. What’s wrong with this picture? Well, a few things. Firstly, setTimeout doesn’t take into account what else is happening in the browser. The page could be hidden behind a tab, hogging your CPU when it doesn’t need to, or the animation itself could have been scrolled off the page making the update call again unnecessary. Chrome does throttle setInterval
and setTimeout
to 1fps in hidden tabs, but this isn’t to be relied upon for all browsers.
Secondly, setTimeout
only updates the screen when it wants to, not when the computer is able to. That means your poor browser has to juggle redrawing the animation whilst redrawing the whole screen, and if your animation frame rate is not in synchronised with the redrawing of your screen, it could take up more processing power. That means higher CPU usage and your computer’s fan kicking in, or draining the battery on your mobile device. Nicolas Zakas does an excellent job explaining the impact timer resolution has on animation in a related article.
Another consideration is the animation of multiple elements at once. One way to approach this is to place all animation logic in one interval with the understanding that animation calls may be running even though a particular element may not require any animation for the current frame. The alternative is to use separate intervals. The problem with this approach is that each time you move something on screen, your browser has to repaint the screen. This is wasteful!
requestAnimationFrame to the rescue!
To overcome these efficiency problems, Mozilla (makers of Firefox) proposed the requestAnimationFrame
function, which was later adopted and improved by the WebKit team (Chrome and Safari). It provides a native API for running any type of animation in the browser, be it using DOM elements, CSS, canvas, WebGL or anything else.
Here is how you use it:
function draw() {
requestAnimationFrame(draw);
// Drawing code goes here
}
draw();
Great! It’s exactly like the setTimeout
version but with requestAnimationFrame
instead. Optionally you can pass a parameter along to the function that’s being called, such as the current element being animated like so: requestAnimationFrame(draw, element)
;
You may have noticed though that you don’t specify an interval rate. So how often is the draw
function called? That all depends on the frame rate of your browser and computer, but typically it’s 60fps (which is cool as your computer’s display typically refreshes at a rate of 60Hz). The key difference here is that you are requesting the browser to draw your animation at the next available opportunity, not at a predetermined interval. It has also been hinted that browsers could choose to optimize performace of requestAnimationFrame
based on load, element visibility (being scrolled out of view) and battery status.
The other beauty of requestAnimationFrame
is that it will group all of your animations into a single browser repaint. This saves CPU cycles and allows your device to live a longer, happier life.
So if you use requestAnimationFrame
all your animations should become silky smooth, synced with your GPU and hog much less CPU. And if you browse to a new tab, the browser will throttle the animation to a crawl, preventing it from taking over your computer whilst you’re busy. Yay!
Any problems?
Because this is a new API it’s only currently available in browsers via a vendor prefix, such as webkitRequestAnimationFrame
in Chrome and Safari, and mozRequestAnimationFrame
in Firefox. Browser support is not bad overall, and even Microsoft only support msRequestAnimationFrame
in IE10+.
To get around the varied support, Eric Möller (Opera), Paul Irish (Google), and Tino Zijdel (Tweakers.net) have created a polyfill to make it simple to use again:
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller
// fixes from Paul Irish and Tino Zijdel
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
Drop this into your code and you can use requestAnimationFrame
as nature intended.
What if I want to set a frame rate?
One glaring problem remaining is this: how do you control the timing of an animation with requestAnimationFrame
if you can’t specify the frame rate? Games typically require specific frame rates for their animations.
Here is a technique you can use:
var fps = 15;
function draw() {
setTimeout(function() {
requestAnimationFrame(draw);
// Drawing code goes here
}, 1000 / fps);
}
By wrapping the requestAnimationFrame
in a setTimeout you get to have your cake and eat it. Your code gets the efficiency savings and you can specify a frame rate, up to 60fps.
A more sophisticated technique would be to check the number of milliseconds past since the last draw
call and update the animation’s position based on the time difference. For example:
var time;
function draw() {
requestAnimationFrame(draw);
var now = new Date().getTime(),
dt = now - (time || now);
time = now;
// Drawing code goes here... for example updating an 'x' position:
this.x += 10 * dt; // Increase 'x' by 10 units per millisecond
}
References & Resources
- http://creativejs.com/resources/requestanimationframe/
Latest Post
- Dependency injection
- Directives and Pipes
- Data binding
- HTTP Get vs. Post
- Node.js is everywhere
- MongoDB root user
- Combine JavaScript and CSS
- Inline Small JavaScript and CSS
- Minify JavaScript and CSS
- Defer Parsing of JavaScript
- Prefer Async Script Loading
- Components, Bootstrap and DOM
- What is HEAD in git?
- Show the changes in Git.
- What is AngularJS 2?
- Confidence Interval for a Population Mean
- Accuracy vs. Precision
- Sampling Distribution
- Working with the Normal Distribution
- Standardized score - Z score
- Percentile
- Evaluating the Normal Distribution
- What is Nodejs? Advantages and disadvantage?
- How do I debug Nodejs applications?
- Sync directory search using fs.readdirSync