Cross Browser and Legacy Supported RequestFrameAnimation

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().

References:

  1. window.requestAnimationFrame on MDN

Legacy Support for HTML 5 Forms

As discussed in the article, HTML 5 Feature Detection Using Modernizr, HTML 5 contains many new input types and attributes. While, most new browsers support them (although often imperfectly), the majority of people still use browsers that do not. However, we can use JavaScript to provided legacy support for the new HTML 5 input attributes and types in browsers that lack support. Toadys article introduces a widget for the YUI 3 gallery, that emulates ...

HTML 5 Feature Detection Using Modernizr

HTML 5 is probably the most exciting improvement to come to the web, since the popularization of AJAX. However, as shown by last weeks article (HTML 5 Forms), only the most cutting edge browsers support many of the new HTML 5 features. To progressively enhance a site for HTML 5 enabled browsers, developers need a way to detect if the browser supports a desired HTML 5 feature. Enter Modernizr by Faruk Ates ...

HTML 5 Forms

HTML 5 is quickly gaining in popularity and is appearing more frequently across the web. While you may hear about the awesomeness of HTML5s new JavaScript and CSS features, the actual HTML improves are often missed. Lets look at the thirteen new form field types and some of the more useful new form field attributes. Not all browsers support HTML 5 forms yet, and those that dont will render the new input types as text ...

Introducing Web Workers

The HTML 5 specification introduces a new technology called Web Workers, allowing developers to spawn new threads for processing JavaScript code. This is a major improvement from the current state of the web, as it allows JavaScript code to execute outside of the UI thread, so your application is still responsive during long-running scripts. However, due to the nature of Web Workers there are restrictions to what they can and cant do, and how to ...