Timing control for script-based animations ("requestAnimationFrame")
Internet Explorer 10 and Windows apps using JavaScript introduce support for the requestAnimationFrame method, which provides a smoother and more efficient way to create animated webpages by calling the animation frame when the system is ready to paint the frame. Prior to this API, animations drawn using setTimeout and setInterval didn't provide web developers an efficient way to schedule graphics timers for animations. This resulted in overdrawn animations, wasted CPU cycles, and extra power usage. Further, animation frequently occurs even when a website isn't visible, particularly when the website uses pages in background tabs or when the browser is minimized.
When an animation uses a JavaScript timer resolution of 10ms to draw animations, you get a timing mismatch as shown here.
The top row represents the 16.7ms display frequency displayed on most monitors and the bottom row represents a typical setTimeout of 10ms. Every third draw cannot be painted (indicated by the red arrow) because another draw request occurs before the display refresh interval. This overdrawing results in choppy animations because every third frame is lost. This timer resolution reduction can also negatively impact battery life, and reduce performance of other apps.
The requestAnimationFrame method (defined in the World Wide Web Consortium (W3C)'s Timing control for script-based animations specification) can solve the lost frame problem because it enables apps to be notified when (and only when) the browser needs to update the page display. As a result, apps are perfectly aligned with the browser painting interval and use only the appropriate amount of resources. Switching from setTimeout to requestAnimationFrame is easy, as they both schedule a single callback. For continued animation, call requestAnimationFrame again after the animation function has been called.
Using requestAnimationFrame
To use this new API, you just call requestAnimationFrame using a callback function. The timing is handled by the system. If you're currently using setTimeout to drive your animation timing, like this:
var handle = setTimeout(renderLoop, PERIOD);
You can replace setTimeout with requestAnimationFrame, as shown here:
var handle = requestAnimationFrame(renderLoop);
This takes care of your first repaint. To keep the animation going, call requestAnimationFrame again from within your callback function (shown as renderLoop here).
<!DOCTYPE html>
<html>
<head>
<title>Script-based animation using requestAnimationFrame</title>
<style type="text/css">
#animated {
position: absolute; top:100px;
padding: 50px; background: crimson; color: white;
}
</style>
</head>
<body>
<div>Click the box to stop and start it</div>
<div id="animated">Hello there.</div>
<script type="text/javascript">
// global variables
var elm = document.getElementById("animated");
var handle = 0;
var lPos = 0;
renderLoop(); // call animation frame to start
function renderLoop() {
elm.style.left = ((lPos += 3) % 600) + "px";
handle = window.requestAnimationFrame(renderLoop);
}
// click the box to stop the animation
document.getElementById("animated").addEventListener("click", function () {
if (handle) {
window.cancelAnimationFrame(handle);
handle = null;
} else {
renderLoop();
}
}, false);
</script>
</body>
</html>
This examples starts by saving the <div> element to a global variable using getElementById. The renderLoop() function is called to start the animation. After the <div> element is repositioned, requestAnimationFrame is called again to set up for the next move. This continues until you close the browser or click the <div> element.
The addEventListener method handles the click event on the <div> element. When you click the element, cancelAnimationFrame is called with the current handle.
Time in a callback function
You can use the callback function to animate almost anything, for example, SVG, Canvas, or as we show here, CSS properties. The callback function has one incoming time parameter, which represents the time the callback function is called. This is a DOMHighResTimeStamp, a high resolution time measured from the beginning of the navigation of the page. DOMHighResTimeStamp is measured in milliseconds accurate to a thousandth of a millisecond. This time value isn't directly comparable with Date.now(), which measures time in milliseconds since 01 January 1970. If you want to compare the time parameter with the current time, use window.performance.now for the current time. Changing the time value from DOMTimeStamp to DOMHighResTimeStamp was a late breaking change in the latest Editors Draft of the W3C Timing control for script-based animation specification, and some vendors might still implement it as DOMTimeStamp. Earlier versions of the W3C specification used the DOMTimeStamp, which allowed you to use Date.now for the current time.
Browser support
As mentioned earlier, some browser vendors might still implement the DOMTimeStamp parameter, or haven't implemented the window.performance.now timing function. Because the W3C specification isn't yet final, other browser manufacturers have created their own prefixed implementations of requestAnimationFrame and cancelAnimationFrame. This example provides a version of our animation program that works across several browsers and uses time to move the <div> element.
<!DOCTYPE html>
<html>
<head>
<title>Script-based animation using requestAnimationFrame</title>
<style type="text/css">
div { position: absolute; left: 10px; top:100px; padding: 50px;
background: crimson; color: white; }
</style>
<script type="text/javascript">
var requestId = 0;
var startime = 0;
var lpos = 0;
var elm;
function init() {
elm = document.getElementById("animated");
}
function render() {
elm.style.left = ((lpos += 3) % 600) + "px";
requestId = window.requestAFrame(render);
}
function start() {
if (window.performance.now) {
startime = window.performance.now();
} else {
startime = Date.now();
}
requestId = window.requestAFrame(render);
}
function stop() {
if (requestId)
window.cancelAFrame(requestId);
}
// handle multiple browsers for requestAnimationFrame()
window.requestAFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
// if all else fails, use setTimeout
function (callback) {
return window.setTimeout(callback, 1000 / 60); // shoot for 60 fps
};
})();
// handle multiple browsers for cancelAnimationFrame()
window.cancelAFrame = (function () {
return window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
function (id) {
window.clearTimeout(id);
};
})();
</script>
</head>
<body onload="init();">
<div id="animated">Hello there.</div>
<button onclick="start()">Start</button>
<button onclick="stop()">Stop</button>
</body>
</html>
This example is an updated version of a sample from the W3C specification, with vender prefixed elements added. The example additionally tests for window.performance.now, and if it isn't supported, uses Date.now to get the current time. If requestAnimationFrame and cancelAnimationFrame aren't supported, then setTimeout and clearTimeout are used. This gives compatibility for earlier versions of the browser.
API Reference
Samples and tutorials
Efficient animations using requestAnimationFrame sample
Internet Explorer Test Drive demos
IEBlog posts
W3C Web Performance: Continuing Performance Investments
Web Performance APIs Rapidly Become W3C Candidate Recommendations
Moving the Stable Web Forward in IE10 Release Preview
The Year in Review: W3C Web Performance Working Group
Using PC Hardware more efficiently in HTML5: New Web Performance APIs, Part 1