More efficient Javascript animations with mozRequestAnimationFrame

This is a re-post from Robert O’Callahan’s blog.

<b>mozRequestAnimationFrame</b> is an experimental API to make Javascript animations more efficient. We do not guarantee to support it forever, and I wouldn’t evangelize sites to depend on it. We’ve implemented it so that people can experiment with it and we can collect feedback. At the same time we’ll propose it as a standard (minus the moz prefix, obviously), and author feedback on our implementation will help us make a better standard.

This feature will be available in Firefox 4 Beta 4.

In Firefox 4 we’ve added support for two major standards for declarative animation — SVG Animation (aka SMIL) and CSS Transitions. However, I also feel strongly that the Web needs better support for JS-based animations. No matter how rich we make declarative animations, sometimes you’ll still need to write JS code to compute (“sample”) the state of each animation frame. Furthermore there’s a lot of JS animation code already on the Web, and it would be nice to improve its performance and smoothness without requiring authors to rewrite it into a declarative form.

Obviously you can implement animations in JS today using setTimeout/setInterval to trigger animation samples and calling Date.now() to track animation progress. There are two big problems with that approach. The biggest problem is that there is no “right” timeout value to use. Ideally, the animation would be sampled exactly as often as the browser is able to repaint the screen, up to some maximum limit (e.g., the screen refresh rate). But the author has no idea what that frame rate is going to be, and of course it can even vary from moment to moment. Under some conditions (e.g. the animation is not visible), the animation should stop sampling altogether. A secondary problem is that when there are multiple animations running — some in JS, and some declarative animations — it’s hard to keep them synchronized. For example you’d like a script to be able to start a CSS transition and a JS animation with the same duration and have agreement on the exact moment in time when the animations are deemed to have started. At each paint you’d also like to have them sampled using the same “current time”.

These problems have come up from time to time on mailing lists, for example on public-webapps. A while ago I worked out an API proposal and Boris Zbarsky just implemented it; it’s in Firefox 4 beta 4. Here’s the API, it’s really simple:

  • window.mozRequestAnimationFrame(): Signals that an animation is in progress, requests that the browser schedule a repaint of the window for the next animation frame, and requests that a MozBeforePaint event be fired before that repaint.
  • The browser fires a MozBeforePaint event at the window before we repaint it. The timeStamp attribute of the event is the time, in milliseconds since the epoch, deemed to be the “current time” for all animations for this repaint.
  • There is also a window.mozAnimationStartTime attribute, also in milliseconds since the epoch. When a script starts an animation, this attribute indicates when that animation should be deemed to have started. This is different from Date.now() because we ensure that between any two repaints of the window, the value of window.mozAnimationStartTime is constant, so all animations started during the same frame get the same start time. CSS transitions and SMIL animations triggered during that interval also use that start time. (In beta 4 there’s a bug that means we don’t quite achieve that, but we’ll fix it.)

That’s it! Here’s an example; the relevant sample code:

var start = window.mozAnimationStartTime;
function step(event) {
  var progress = event.timeStamp - start;
  d.style.left = Math.min(progress/10, 200) + "px";
  if (progress < 2000) {
    window.mozRequestAnimationFrame();
  } else {
    window.removeEventListener("MozBeforePaint", step, false);
  }
}
window.addEventListener("MozBeforePaint", step, false);
window.mozRequestAnimationFrame();

It's not very different from the usual setTimeout/Date.now() implementation. We use window.mozAnimationStartTime and event.timeStamp instead of calling Date.now(). We call window.mozRequestAnimationFrame() instead of setTimeout(). Converting existing code should usually be easy. You could even abstract over the differences with a wrapper that calls setTimeout/Date.now if mozAnimationStartTime/mozRequestAnimationFrame are not available. Of course, we want this to become a standard so eventually such wrappers will not be necessary!

Using this API has a few advantages, even in this simple case. The author doesn't have to guess a timeout value. If the browser is overloaded the animation will degrade gracefully instead of uselessly running the step script more times than necessary. If the page is in a hidden tab, we'll be able to throttle the frame rate down to a very low value (e.g. one frame per second), saving CPU load. (This feature has not landed yet though.)

One important feature of this API is that mozRequestAnimationFrame is "one-shot". You have to call it again from your event handler if your animation is still running. An alternative would be to have a "beginAnimation"/"endAnimation" API, but that seems more complex and slightly more likely to leave animations running forever (wasting CPU time) in error situations.

This API is compatible with browser implementations that offload some declarative animations to a dedicated "compositing thread" so they can be animated even while the main thread is blocked. (Safari does this, and we're building something like it too.) If the main thread is blocked on a single event for a long time (e.g. if a MozBeforePaint handler takes a very long time to run) it's obviously impossible for JS animations to stay in sync with animations offloaded to a compositing thread. But if the main thread stays responsive, so MozBeforePaint events can be dispatched and serviced between each compositing step performed by the compositing thread, I think we can keep JS animations in sync with the offloaded animations. We need to carefully choose the animation timestamps returned by mozAnimationStartTime and event.timeStamp and dispatch MozBeforePaint events "early enough".

EDIT: The mozRequestAnimationFrame Frame Rate Limit
(from Robert O'Callahan’s blog)

A few people have been playing with mozRequestAnimationFrame and noticed that they can't get more than 50 frames per second. This is intentional, and it's a good feature.

On modern systems an application usually cannot get more than 50-60 frames per second onto the screen. There are multiple reasons for this. Some of them are hardware limitations: CRTs have a fixed refresh rate, and LCDs are also limited in the rate at which they can update the screen due to bandwidth limitations in the DVI connector and other reasons. Another big reason is that modern operating systems tend to use "compositing window managers" which redraw the entire desktop at a fixed rate. So even if an application updates its window 100 times a second, the user won't be able to see more than about half of those updates. (Some applications on some platforms, typically games, can go full-screen, bypass the window manager and get updates onto the screen as fast as the hardware allows, but obviously desktop browsers aren't usually going to do that.)

So, firing a MozBeforePaint event more than about 50 times a second is going to achieve nothing other than wasting CPU (i.e., power). So we don't. Apart from saving power, reducing animation CPU usage helps overall performance because we can use the free time to perform garbage collection or other house-cleaning tasks, reducing the incidence or length of frame skips.

We need to do some followup work to make sure that on each platform we use the optimal rate; modern platforms have APIs to tell us the window manager's composition rate. But 50Hz is almost always pretty close.

This all means that measuring FPS is a bad way to measure performance, once you're up to 50 or more. At that point you need to increase the difficulty of your workload.

Tell us what you think.

About Paul Rouget

Paul is a Firefox developer.

More articles by Paul Rouget…


14 comments

  1. Luca

    What about registering the callback(s) (step) directly with mozRequestAnimationFrame?

    August 16th, 2010 at 06:25

  2. Robert O’Callahan

    That’s not a bad idea…

    August 17th, 2010 at 02:31

  3. Julián Ceballos

    That’s a great idea for browser, it could make so much better the graphics animation with javascript and don’t force the browser to simulate an animation with just changing images or pixels values.

    August 21st, 2010 at 22:49

  4. Daniel Cassidy

    If I understand correctly, this is vsync() for JavaScript. If so, then frankly it’s about bloody time someone implemented this. I couldn’t believe it when I found out that canvas has no way to sync with the refresh rate.

    However – are you saying you’ve hard limited the frame rate to 50Hz? If so, that’s crazy. I’ve never heard of a monitor refreshing at less than 60Hz, so you’re arbitrarily forcing JS animations to skip at least 1/6 of all frames, which will lead to unnecessarily jerky animation.

    If, on the other hand, you’re just saying that MozBeforePaint won’t fire more often than the refresh rate then yes, obviously that’s the correct thing to do. I’m baffled as to why anyone would complain about that.

    August 26th, 2010 at 14:46

  5. how.,e

    i say we keep tween type class that uses data structure similar to timeline animation .. . IE adding KeyFrame(s) to a animation sequence rather then Calling a Function thats uses callbacks to run next keyframe/animation

    ex .. AddAnimation({duration:5, x:5},{duration:10, x:20});
    this basically adds two keyframes.

    I think with this approach people can build tools for animating in more traditional ways which can open up alot more possibilities .

    September 4th, 2010 at 17:11

  6. louis-rémi

    What about an alternative to setInterval that will always fire the callback before the repaint, and thus, doesn’t need any miliseconds param.

    setFrameInterval(function() {
    // do something before I paint
    });

    Having worked on the animation part of jQuery, this seems easier to leverage.

    October 21st, 2010 at 11:32

  7. Evgeny

    In my canvas implementation for IE I am using this trick:
    canvas.onframe = function(){
    // … drawing routines
    }
    Thus the animation syncing with flashes frame rate.

    November 7th, 2010 at 01:54

  8. Reyboz Blog

    Che dire.. Complimenti per il progetto!

    December 13th, 2010 at 12:42

  9. Victor

    i think, window.mozAnimationStartTime better replace with function

    window.mozAnimationStartTime(), so we can create javascript polyfil for this API and use it in old browsers…
    (with property we can’t because of getters isn’t supported but some browsers)

    January 16th, 2011 at 23:44

  10. Jorge

    “…But 50Hz is almost always pretty close.” made me recall “640K of memory should be enough for anybody.”.

    And then most new TV sets are doing 120Hz.
    I don’t like fixed numbers, there’s no reason for that. Throttling according to hardware or setting it as an option with 50 as a default would be much better and future-proof.

    March 24th, 2011 at 11:34

  11. Joe

    In FireFox 3.6 or 4, I found that attaching your draw code to ‘mozBeforePaint’ made your code update slightly faster then if you passed it into ‘requestAnimationFrame’. This was like running at 60fps, instead of 58fps, so it was very minor.

    In FireFox 6, on Windows, I am finding that using mozBeforePaint causes a 30% drop in performance! Literally 40fps, instead of 60fps.

    So I’d recommend passing your function into ‘requestAnimationFrame’ instead of using ‘mozBeforePaint’.

    August 26th, 2011 at 16:59

  12. milo

    I build a Menucool jQuery Slider(www.menucool.com/slider/jquery-slider) that animate smoothly in most browsers after utilizing requestAnimationFrame, but not Firefox. It looks chunky and jumpy in Firefox even if I set the fms to be 50, and I believe it is the mozRequestAnimationFrame a little bit not as efficient as the webkitRequestAnimationFrame or msRequestAnimationFrame.

    How can I make it as smooth in FF as in other browsers? Can anyone help me to get a workaround for this?

    June 30th, 2012 at 18:52

  13. milo

    PS: The jQuery Slider I mentioned above is actually using a pure JavaScript. It did not use the jQuery library as jQuery has taken off the requestAnimationFrame since version 1.6.

    June 30th, 2012 at 18:58

  14. Greg

    I’ve built an entire game build from this idea, I think it’s FANTASTIC, I really hope it’s well liked enough that you do come to support it!

    October 20th, 2012 at 12:36

Comments are closed for this article.