Better JavaScript animations with requestAnimationFrame
For a long time, timers and intervals have been the state of the art for JavaScript-based animations. While CSS transitions and animations make some animations easy for web developers, little has changed in the world of JavaScript-based animation over the years. That is, until Firefox 4 was released with the first way to improve JavaScript animations. But to fully appreciate the improvement, it helps to take a look at how animations have evolved on the web.
Timers
The very first pattern for creating animations was to use chained setTimeout() calls. Long-time developers will remember the obsession with statusbar news tickers that littered the web during Netscape 3′s hayday. It usually looked something like this:
(function(){
var msg = "NFL Draft is live tonight from Radio City Music Hall in New York City!"
len = 25,
pos = 0,
padding = msg.replace(/./g, " ").substr(0,len)
finalMsg = padding + msg;
function updateText(){
var curMsg = finalMsg.substr(pos++, len);
window.status = curMsg;
if (pos == finalMsg.length){
pos = 0;
}
setTimeout(updateText, 100);
}
setTimeout(updateText, 100);
})();
If you want to test this code out in a browser, create a <pre> element and use that instead of window.status, as I did this newsticker example.
This annoying web pattern was later countered with restrictions on window.status, but the basic technique re-emerged with the release of Internet Explorer 4 and Netscape 4, the first browsers to give developers more control over how elements were laid out on the page. With that, came the ability to dynamically change the size, location, color, etc. of elements using JavaScript, and a whole new breed of animations. For example. the following animates a <div> to a width of 100% (often found in progress bars):
(function(){
function updateProgress(){
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.width != "100%"){
setTimeout(updateProgress, 100);
}
}
setTimeout(updateProgress, 100);
})();
Even though the animated parts of the page were different, the basic technique remained the same: make a change, use setTimeout() to yield and let the page update, then the timer would be called to apply the next change. This process repeated until the animation was complete (see the progressbar in action). Same technique as the early status scrollers, just a different animation.
Chaining calls to setTimeout() together, as in both of these examples, creates an animation loop. Animation loops are used in computer programs to handle updating a user interface at regular intervals. All animation loops operate the same way: make an update, sleep, make an update, sleep. Early on, setTimeout() was the primary animation loop technique for JavaScript.
Intervals
With the successful re-introduction of animations to the web (much to the dismay of purists like myself), came new explorations. It was no longer good enough to have just one animation, there had to be multiple. The first attempts were to create multiple animation loops, one for each animation. Creating multiple timers using setTimeout() proved to be a bit much for these early browsers to handle, and so developers began using a single animation loop, created with setInterval(), to manage all of the animations on the page. A basic animation loop using setInterval() looks like this:
(function(){
function updateAnimations(){
updateText();
updateProgress();
}
setInterval(updateAnimations, 100);
})();
To build out a small animation library, the updateAnimations() method would cycle through the running animations and make the appropriate changes to each one (see both a news ticker and a progressbar running together). If there are no animations to update, the method can exit without doing anything and perhaps even stop the animation loop until more animations are ready for updating.
The tricky part about this animation loop is knowing what the delay should be. The interval has to be short enough to handle a variety of different animation types smoothly but long enough so as to produce changes the browser could actually render. Most computer monitors refresh at a rate of 60 Hz, which basically means there’s a repaint 60 times per second. Most browsers cap their repaints so they do not attempt to repaint any more frequently than that, knowing that the end user gets no improvement in experience.
Given that, the best interval for the smoothest animation is 1000ms / 60, or about 17ms. You’ll see the smoothest animation at this rate because you’re more closely mirroring what the browser is capable of doing. Compare this example with a 17ms interval to the previous example and you’ll see a much smoother animation (also much faster because the animations are updating more frequently and I’ve not done any calculation to take that into effect). Multiple animations may need to be throttled so as not to complete too quickly when using an animation loop with a 17ms interval.
The problem(s)
Even though setInterval()-based animation loops are more efficient than having multiple sets of setTimeout()-based loops, there are still problems. Neither setInterval() nor setTimeout() are intended to be precise. The delay you specify as the second argument is only an indication of when the code is added in the browser’s UI thread queue for possible execution. If there are other jobs in the queue ahead of it, then that code waits to be executed. In short: the millisecond delay is not an indication of when the code will be executed, only an indication of when the job will be queued. If the UI thread is busy, perhaps dealing with user actions, then that code will not execute immediately.
Understanding when the next frame will be drawn is key to smooth animations, and until recently, there was no way to guarantee when the next frame would be drawn in a browser. As <canvas> became popular and new browser-based games emerged, developers became increasingly frustrated with the inaccuracy of setInterval() and setTimeout().
Exacerbating these problems is the timer resolution of the browser. Timers are not accurate to the millisecond. Here are some common timer resolutions[1]:
- Internet Explorer 8 and earlier have a timer resolution of 15.625ms
- Internet Explorer 9 and later have a timer resolution of 4ms.
- Firefox and Safari have a timer resolution of ~10ms.
- Chrome has a timer resolution of 4ms.
Internet Explorer prior to version 9 has a timer resolution of 15.625 ms[1], so any value between 0 and 15 could be either 0 or 15 but nothing else. Internet Explorer 9 improved timer resolution to 4 ms, but that’s still not very specific when it comes to animations. Chrome’s timer resolution is 4ms while Firefox and Safari’s is 10ms. So even if you set your interval for optimum display, you’re still only getting close to the timing you want.
mozRequestAnimationFrame
Robert O’Callahan of Mozilla was thinking about this problem and came up with a unique solution. He pointed out that CSS transitions and animations benefit from the browser knowing that some animation should be happening, and so figures out the correct interval at which to refresh the UI. With JavaScript animations, the browser has no idea that an animation is taking place. His solution was to create a new method, called mozRequestAnimationFrame(), that indicates to the browser that some JavaScript code is performing an animation. This allows the browser to optimize appropriately after running some code.
The mozRequestAnimationFrame() method accepts a single argument, which is a function to call prior to repainting the screen. This function is where you make appropriate changes to DOM styles that will be reflected with the next repaint. In order to create an animation loop, you can chain multiple calls to mozRequestAnimationFrame() together in the same way previously done with setTimeout(). Example:
function updateProgress(){
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.left != "100%"){
mozRequestAnimationFrame(updateProgress);
}
}
mozRequestAnimationFrame(updateProgress);
Since mozRequestAnimationFrame() only runs the given function once, you need to call it again manually the next time you want to make a UI change for the animation. You also need to manage when to stop the animation in the same way. Pretty cool, and the result is a very smooth animation as seen in this enhanced example.
So far, mozRequestAnimationFrame() has solved the problem of browsers not knowing when a JavaScript animation is happening and the problem of not knowing the best interval, but what about the problem of not knowing when your code will actually execute? That’s also covered with the same solution.
The function you pass in to mozRequestAnimationFrame() actually receives an argument, which is a time code (in milliseconds since January 1, 1970) for when the next repaint will actually occur. This is a very important point: mozRequestAnimationFrame() actually schedules a repaint for some known point in the future and can tell you when that is. You’re then able to determine how best to adjust your animation.
In order to determine how much time has passed since the last repaint, you can query mozAnimationStartTime, which contains the time code for the last repaint. Subtracting this value from the time passed into the callback allows you to figure out exactly how much time will have passed before your next set of changes are drawn to the screen. The typical pattern for using these values is as follows:
function draw(timestamp){
//calculate difference since last repaint
var diff = timestamp - startTime;
//use diff to determine correct next step
//reset startTime to this repaint
startTime = timestamp;
//draw again
mozRequestAnimationFrame(draw);
}
var startTime = mozAnimationStartTime;
mozRequestAnimationFrame(draw);
The key is to make the first call to mozAnimationStartTime outside of the callback that is passed to mozRequestAnimationFrame(). If you call mozAnimationStartTime inside of the callback, it will be equal to the time code that is passed in as an argument.
webkitRequestAnimationFrame
The folks over at Chrome were clearly excited about this approach and so created their own implementation called webkitRequestAnimationFrame(). This version is slightly different than the Firefox version in two ways. First, it doesn’t pass a time code into the callback function, you don’t know when the next repaint will occur. Second, it adds a second, optional argument which is the DOM element where the changes will occur. So if you know the repaint will only occur inside of one particular element on the page, you can limit the repaint to just that area.
It should come as no surprised that there is no equivalent mozAnimationStartTime, since that information without the time of the next paint is not very useful. There is, however, a webkitCancelAnimationFrame(), which cancels the previously scheduled repaint.
If you don’t need precision time differences, you can create an animation loop for Firefox 4+ and Chrome 10+ with the following pattern:
(function(){
function draw(timestamp){
//calculate difference since last repaint
var drawStart = (timestamp || Date.now()),
diff = drawStart - startTime;
//use diff to determine correct next step
//reset startTime to this repaint
startTime = drawStart;
//draw again
requestAnimationFrame(draw);
}
var requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame,
startTime = window.mozAnimationStartTime || Date.now();
requestAnimationFrame(draw);
})();
This pattern uses the available features to create an animation loop with some idea of how much time has passed. In Firefox, this uses the time code information that is available while Chrome defaults to the less-accurate Date object. When using this pattern, the time difference gives you a general idea of how much time has passed but certainly isn’t going to tell you the next time a repaint will occur in Chrome. Still, it’s better to have some idea of how much time has passed rather than none.
Wrap up
The introduction of the mozRequestAnimationFrame() method is the most significant contribution to improving JavaScript animations perhaps in the history of the web. As discussed, the state of JavaScript animation has pretty much been the same since the early days of JavaScript. With browsers getting better at animation and the introduction of CSS transitions and animations, it’s nice to see some attention being paid to JavaScript-based animations, as these will mostly certainly become more important and more CPU-intensive with the proliferation of <canvas>-based games. Knowing when JavaScript is attempting animation allows browsers to do more optimal processing, including stopping that processing when a tab is in the background or when the battery on a mobile device is running low.
The requestAnimationFrame() API is now being drafted as a new recommendation by the W3C and is being worked on jointly by Mozilla and Google as part of the Web Performance group. It’s good to see the two groups moving so quickly to get compatible (if not completely) implementations out into the wild.
Update (03-May-2011): Fixed typo, added mobile information.
Update (04-May-2011): Fixed link to enhanced example.
References
- Chrome: Cranking up the clock, by Mike Belshe
- requestAnimationFrame implementation (Chrome)
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




12 Comments
Very cool! By the way, your ‘enhanced example’ link points to the wrong page (progressbar-2 instead of progressbar-3).
Will on May 3rd, 2011 at 11:40 pm
Nice explanation of the requestAnimationFrame draft!
I read about it earlier on Paul Irish’s blog, but this article explains it more clearly (to me, that is).
If I understand this correctly, the browser tells your function: ‘Hey, I’m going to repaint in x amount of milliseconds. If you want me to run some code right before that, now’s your chance to tell me.” If so: won’t an animation that requires quite some processing time slow down repaints?
Also: does the Chrome implementation only repaint the DOM element you pass in? If not: why do you have the option of specifying such a parameter?
——–
P.S.: Your advanced example links to http://www.nczonline.net/experiments/animation/newsticker-and-progressbar-2.htm as well, instead of http://www.nczonline.net/experiments/animation/newsticker-and-progressbar-3.htm
Kristiaan Van den Eynde on May 4th, 2011 at 10:31 am
Scratch that last question, I overlooked the line that says:
“So if you know the repaint will only occur inside of one particular element on the page, you can limit the repaint to just that area.”
/sillyme
Kristiaan Van den Eynde on May 4th, 2011 at 10:34 am
Thanks for pointing out the problem link, I’ve fixed it.
@Kristiaan – You understood everything correctly. If the function you pass into
requestAnimationFrame()takes a long time, then you will slow down the repaint frequency.Nicholas C. Zakas on May 4th, 2011 at 1:29 pm
Hi, nice article. Two points/questions:
– Last time I checked, (moz|webkit)RequestAnimationFrame method was scoped and in order to get the same functionality into a more generic function you’d need to enclose those in an anonymous function:
var requestAnimationFrame = (function() {
return window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
})();
I’m not sure if this has been solved in the newer browser versions, but in that case the last code snippet you posted wouldn’t work.
- Could you enumerate the differences between using setTimeout vs. setInterval for just one big loop? Isn’t also the process of setting up a new timer each time instead of re-using the same timer a performance improvement?
Nicolas on May 6th, 2011 at 1:47 pm
@Nicolas – I don’t think the methods are scoped. My last example uses the boilerplate I mentioned and it seems to be working fine. The issue of timers vs. intervals has already been discussed on the interwebs.
Nicholas C. Zakas on May 7th, 2011 at 6:30 pm
Demo 2 and 3 run with no discernible difference between the two for me, in both FF4 and Chrome11-beta – why might that be?
JGarrido on May 9th, 2011 at 12:47 pm
@JGarrido – I never said the differences would be discernible in the examples, these were just intended to show the various ways of doing it. Where you’d really see differences is in games that require constant animation.
Nicholas C. Zakas on May 10th, 2011 at 2:12 pm
[...] At last, some science to how horrible JS animation is in “certain” browsers, and at least the beginning of a proper solution… [...]
Today’s Readings | Aaron T. Grogg on May 11th, 2011 at 5:15 pm
Coming from a Flash background I’ve always thought about animations (and video) in terms of “framerate”. How would this concept carry over to requestAnimationFrame? Does it just draw as fast as it can?
Marcus Booster on May 14th, 2011 at 1:06 pm
What puzzles me is that this new technique seems to alter the problem of animation, but not resolve it.
Previously, setInterval would have issues with high-load callbacks being skipped due to there already being an uninitialized callback in the code queue. The solution was chaining setTimeout calls with the same drawback, however, of having an unpredictable delay in between executions.
With requestAnimationFrame, the browser tells you when it will start executing a function (before the repaint), but there is still a possibility of ‘lag’ when the callback(s) requires some time to process. If the browser will not repaint before the callbacks have been executed, you could be showing a frame that was intended to be shown half a second earlier.
Especially when multiple callbacks are being tied (throttled) to the next repaint, there will be quite some processing time before the browser actually repaints the next frame.
So if I understand this new functionality correctly, it will be amazing for very precisely timed animations; as long as the callbacks have a low processing cost. As soon as you tie multiple, heavy callbacks to requestAnimationFrame, however, you’re still facing a very chunky, unpredictable animation.
Or am I completely missing the point here?
Kristiaan Van den Eynde on May 16th, 2011 at 6:21 am
@Marcus – every call to
requestAnimationFrame()basically does what it says: requests another frame be drawn as soon as possible after the function is executed. Up until now, the framerate was close to arbitrary as far as JavaScript was concerned. Adding this method allows better control of the framerate during your animation even though the browser will optimize it.@Kristiaan – I think you’re exaggerating the problem just a bit. In both the interval and frame patterns you mention, you’re calling out heavy processing functions as the problem. That problem is a developer problem not a browser problem. It’s fairly common knowledge that performing a lot of calculations and expecting a smooth animation is a faulty approach. Just, your framerate can drop if you do too much in the specified function, but it will still be far more efficient than using intervals or timers. One of the biggest wins for this new pattern is the browser’s ability to throttle framerate in cases where a device has low power, or a tab is moved to the background, etc. Even so, it is up to the developer to make the best use of this functionality and not treat it as a solve-everything prospect.
Nicholas C. Zakas on May 16th, 2011 at 6:27 pm
Comments are automatically closed after 14 days.