There is an awesome new feature in HTML 5, window.requestAnimationFrame, which tells the browser that you wish to perform an animation and requests that the browser schedule a repaint of the window for the next animation frame1. This allows you to do animations more efficiently and without using setInterval or series of setTimeout function(s).
I have created a Demo that attempts to use the browser’s requestAnimationFrame side-by-side with the legacy polyfill executing.
Getting ready
If your browser supports HTML 5, then the requestAnimationFrame and cancelAnimationFrame will be set by:
// most browsers have an implementation window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame;
If your browser does not support HTML 5, then the requestAnimationFrame and cancelAnimationFrame will be emulated by:
var aAnimQueue = [],
iRequestId = 0,
iIntervalId;
// create a mock requestAnimationFrame function
w.requestAnimationFrame = function(callback) {
aAnimQueue.push([++iRequestId, callback]);
if (!iIntervalId) {
iIntervalId = setInterval(function() {
if (aAnimQueue.length) {
aAnimQueue.shift()[1](+new Date());
}
else {
// don't continue the interval, if unnecessary
clearInterval(iIntervalId);
iIntervalId = undefined;
}
}, 1000 / 50); // estimating support for ~50 frames per second
}
return iRequestId;
};
// create a mock cancelAnimationFrame function
w.cancelAnimationFrame = function(requestId) {
// find the request ID and remove it
for (var i = 0, j = aAnimQueue.length; i < j; i += 1) {
if (aAnimQueue[i][0] === requestId) {
aAnimQueue.splice(i, 1);
return;
}
}
};
Here is a complete code snippet that you can include on your site to support using requestAnimationFrame in all browsers, including legacy:
(function(w) {
"use strict";
// most browsers have an implementation
w.requestAnimationFrame = w.requestAnimationFrame ||
w.mozRequestAnimationFrame || w.webkitRequestAnimationFrame ||
w.msRequestAnimationFrame;
w.cancelAnimationFrame = w.cancelAnimationFrame ||
w.mozCancelAnimationFrame || w.webkitCancelAnimationFrame ||
w.msCancelAnimationFrame;
// polyfill, when necessary
if (!w.requestAnimationFrame) {
var aAnimQueue = [],
iRequestId = 0,
iIntervalId;
// create a mock requestAnimationFrame function
w.requestAnimationFrameLegacy = function(callback) {
aAnimQueue.push([++iRequestId, callback]);
if (!iIntervalId) {
iIntervalId = setInterval(function() {
if (aAnimQueue.length) {
aAnimQueue.shift()[1](+new Date());
}
else {
// don't continue the interval, if unnecessary
clearInterval(iIntervalId);
iIntervalId = undefined;
}
}, 1000 / 50); // estimating support for 50 frames per second
}
return iRequestId;
};
// create a mock cancelAnimationFrame function
w.cancelAnimationFrameLegacy = function(requestId) {
// find the request ID and remove it
for (var i = 0, j = aAnimQueue.length; i < j; i += 1) {
if (aAnimQueue[i][0] === requestId) {
aAnimQueue.splice(i, 1);
return;
}
}
};
}
})(window);
How it works…
The requestAnimationFrame function takes a single argument, the callback to be invoked before the repaint, and returns a positive integer that is the requestID (this will be greater than zero, but do not make any other assumptions), which can be used to stop an animation. You can stop the animation by passing requestID to the cancelAnimationFrame function. The legacy polyfill functions have the exact same signatures, but use an interval for managing animation frames.
The legacy requestAnimationFrame appends the callback function and an internal requestID to an animation queue. The requestID is simply a number that increments each time the function is called, ensuring uniqueness. If an animation setInterval is not running, one is started, and the requestID is returned. When there are items in the queue, the setInterval callback function shifts the first element in the queue and calls the animation function, otherwise the interval is terminated for performance.
The legacy cancelAnimationFrame searches the animation queue for the animation with the matching requestID, then removes it from the queuing array. It does not worry about stopping the interval, as the interval will stop automatically, if the animation queue is empty, on its next cycle.
There’s more…
There is another related property, animationStartTime, which is supported (prefixed) by some browsers, and indicates when the animation frames began. However, (at the time of this writing) browsers implement it and the timestamp passed into the animation callback function differently. Chrome sets this property to zero and passes the difference (in ms) since the animationStartTime to the animation callback function, while FireFox sets the property to ms since the epoch and passes ms since epoch into the callback function. Until a common standard is agreed upon between the browsers, it is better to implement your own start time system (if you need it), using (new Date()).getTime() or the shorter +new Date().