public
Last active

requestAnimationFrame polyfill

  • Download Gist
rAF.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
 
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
 
// MIT license
 
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());

Not a bad shim. A few improvements could be made, though:

1.) use Date.now() instead of new Date().getTime(), avoids one object allocation

2.) Date.now() and setTimeout() operate in terms of integer milliseconds, but 60fps doesn't map to an integer number of milliseconds. This tries to map to 16ms/frame, which is 62.5 and will produce aliasing (meaning bad looking frames) every few seconds. See https://lists.webkit.org/pipermail/webkit-dev/2011-September/018095.html for a very wordy description of why. It is possible to do better, however, by tracking the error between the actual callback rate and the target. http://trac.webkit.org/browser/trunk/Source/WebCore/platform/graphics/chromium/cc/CCDelayBasedTimeSource.cpp?rev=104644#L101 describes in detail how to do this with some C++ code

3.) It's slightly better to set the timer before ticking rather than after - you don't have to worry about setTimeout() clamping if the callback takes >12ms to run and the precision errors are less of a concern. The easiest way to do this is to setTimeout() before invoking the callbacks assuming that somebody will call requestAnimationFrame() within the ticks, and then if they don't end up doing that call clearTimeout() after running the callbacks.

1) Date.now() doesnt exist in oldIE.. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/now so how about +new Date

2) true. dunno how else we can tackle this in JS land though..

3) can i ask you to fork this and make those changes? I can then review and pull 'em in.

Current Chrome (16) supports webkitRequestAnimationFrame but not webkitCancelAnimationFrame (or webkitCancelRequestAnimationFrame). This breaks the polyfill because it starts the animation loop with requestAnimationFrame, but there's no way to stop it. I've patched the polyfill to return the setTimeout in this case

Not sure how to send a pull request for a gist...

It should be CancelRequestAnimationFrame not RequestCancelAnimationFrame. The only references to RequestCancelAnimationFrame are this gist.

webkitCancelRequestAnimationFrame exists in current Chrome (16.0.912.75)

lol dyslexia. thx also. Fixed.

fwiw, the new name is present in canary.

I ran into an issue with Firefox 10, where mozRequestAnimationFrame is supported, but not an equivalent for cancelAnimationFrame. So I was able to start an animation, but not properly stop it. if cancelAnimationFrame is a deal-breaker for you then https://gist.github.com/1866474 might be more your flavor.

Why cAF is not at all in FF10 is a complete mystery to me. Seems no tickets at all filed about it either...

couldn't you simulate cAF by wrapping requestAnimationFrame in a function grabs the ID and passes in a wrapped function that only calls its inner function if cancelAnimationFrame has not been called with a particular ID? (which you can check by having your fake cancelAnimation set the passed in ID on a private/closure variable object to true, and then checking that key in the wrap function.)
or maybe that's all just too complicated/too much overhead etc.

I tried this out on an iOS Phonegap project, and it runs at a really terrible frame rate on my iPhone 4. I switched back to using a simple setTimeout and am getting close to 60fps. The timeToCall calculation is not good on iOS.

I think the timeToCall calculation is mostly useless anyway. It falls apart as soon as you call RAF twice in a row. And as mentioned by the commenter above adds a bunch of overhead.

@paulirish [edit: whoops, was looking at a previous version of the gist. Removing some of the criticism here.]

To handle @taybenlor’s concern about calling RAF multiple times in a row, we can do something like:

var targetTime = 0;
if (!window.requestAnimationFrame)
  window.requestAnimationFrame = function(callback) {
    var currentTime = +new Date;
    targetTime = Math.max(targetTime + 16, currentTime);
    var timeoutCb = function() { callback(+new Date); }
    return window.setTimeout(timeoutCb, targetTime - currentTime);
  };

This also clarifies the code a bit (the previous variable names were a bit confusing), and makes sure that the timestamp passed to the callback is correct, because it is determined (by getting +new Date again) right as the callback is being called.

@jrus I think you misunderstand what is going on

The time passed into the callback is currTime + timeToCall which gives you the time at which we expect the callback to be called (not necessarily when it will actually be called).

@taybenlor yeah, but the semantics of requestAnimationFrame (as far as I can understand from reading the spec; it’s not entirely clear/explicit what time should be passed to the callback) should send the time when it’s actually called into the callback. [Also, I edited my previous comment a bit: was looking at the previous version of the gist when I first wrote it.]

Here’s a cute CoffeeScript port of the whole thing

do ->
    w = window
    for vendor in ['ms', 'moz', 'webkit', 'o']
        break if w.requestAnimationFrame
        w.requestAnimationFrame = w["#{vendor}RequestAnimationFrame"]
        w.cancelAnimationFrame = (w["#{vendor}CancelAnimationFrame"] or
                                  w["#{vendor}CancelRequestAnimationFrame"])

    targetTime = 0
    w.requestAnimationFrame or= (callback) ->
        targetTime = Math.max targetTime + 16, currentTime = +new Date
        w.setTimeout (-> callback +new Date), targetTime - currentTime

    w.cancelAnimationFrame or= (id) -> clearTimeout id

@breton, @geddesign this version should I think do cAF properly in browsers that only implement rAF, assuming they actually return a unique ID for each call to rAF:

do ->
    w = window
    for vendor in ['ms', 'moz', 'webkit', 'o']
        break if w.requestAnimationFrame
        w.requestAnimationFrame = w["#{vendor}RequestAnimationFrame"]
        w.cancelAnimationFrame = (w["#{vendor}CancelAnimationFrame"] or
                                  w["#{vendor}CancelRequestAnimationFrame"])

    # deal with the case where rAF is built in but cAF is not.
    if w.requestAnimationFrame
        return if w.cancelAnimationFrame
        browserRaf = w.requestAnimationFrame
        canceled = {}
        w.requestAnimationFrame = (callback) ->
            id = browserRaf (time) ->
                if id of canceled then delete canceled[id]
                else callback time
        w.cancelAnimationFrame = (id) -> canceled[id] = true

    # handle legacy browsers which don’t implement rAF
    else
        targetTime = 0
        w.requestAnimationFrame = (callback) ->
            targetTime = Math.max targetTime + 16, currentTime = +new Date
            w.setTimeout (-> callback +new Date), targetTime - currentTime

        w.cancelAnimationFrame = (id) -> clearTimeout id

I created a script somewhat based on this gist.
You find the code at: https://github.com/erik-landvall/animator

Suggestion:

var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;

to

lastTime = currTime + timeToCall;
return window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);

Hello!

There is my version of the polyfill

It has a lot of size and performance optimizations

Just some minor tweaks in case anyone is interested: implemented the single var pattern and LeoDutra's suggestion.

(function() {
    var lastTime = 0,
        vendors = ['ms', 'moz', 'webkit', 'o'],
        x,
        length,
        currTime,
        timeToCall;

    for(x = 0, length = vendors.length; x < length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = 
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            currTime = new Date().getTime();
            timeToCall = Math.max(0, 16 - (currTime - lastTime));
            lastTime = currTime + timeToCall;
            return window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

Just downloaded the code, really easy to use and effective.
Thank you!

Would it make sense to augment this shim so that the setTimeout fallback uses performance.webkitNow or performance.now to better determine a good value to setTimeout?

Maybe it's horribly overkill, but if you can multiple animations at the same time. It could pay of to use a list and avoid multiple setTimeout's, also making sure the requests runs at the same time.
I posted my approach here: https://gist.github.com/4438815

Although, it's probably overkill for normal usage...

I've been testing this version out and for some reason i can't cancel the animation request in webkit browsers. It does cancel in Firefox though. Has anyone else run into this?

Note that the latest browsers (confirmed in Chrome, Firefox and IE10) have moved away from Date-based timestamps on requestAnimationFrame. They now all use the new precision API.

If you are having issues during the first 1 or 2 frames, and you are substracting Date.now or something similar, that's the reason.

What is the licensing for this code? Is it reusable for commercial software?

The rAF / cAF shim above for browsers that don't support it (read: IE9) was completely broken for me. In my app I call cAF if rAF is called before a previous callback completes, otherwise some serious lag happens. With the shim it was basically adding 16 ms for each callback so if a whole bunch of events were fired off, the actual final non-cancelled callback might not happen until many seconds later. Hence, I subtracted the 16 ms for each cAF. This works much better in IE9; the part I changed is as follows

        # handle legacy browsers which don't implement rAF
        targetTime = 0
        w.requestAnimationFrame = (callback) ->
            targetTime = Math.max targetTime + 16, currentTime = +new Date
            w.setTimeout (-> callback +new Date), targetTime - currentTime            

        w.cancelAnimationFrame = (id) ->
            targetTime -= 16
            clearTimeout id

You can remove the o prefix, it won't ever be implemented.

You can remove the o prefix, it won't ever be implemented.

@mzgol How do you know that?

I know I'm super late to the game here, but I actually needed to adjust this polyfill because I am making use of the time argument -- which mean I not only need to fill the method, I also need to overwrite any window.requestAnimationFrame call that doesn't give me time.

Here's my go (note: I didn't rewrite the cancelAnimationFrame polyfill, so I didn't include it here)

var lastTime = 0,
    vendors = ['ms', 'moz', 'webkit', 'o'],
    polyfill = function(callback, element) {
        var currTime = new Date().getTime(),
            timeToCall = Math.max(0, 16 - (currTime - lastTime)),
            id = window.setTimeout(function() { 
                callback(currTime + timeToCall); 
            }, timeToCall);

        lastTime = currTime + timeToCall;

        return id;
    };
for(var x = 0, xx = vendors.length; x < xx && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                               || window[vendors[x]+'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = polyfill;
} else {
    window.requestAnimationFrame(function() {
        var isTime = false,
            args = Array.prototype.slice.apply(arguments);
        for (var i = 0, ii = args.length; i < ii; i++) {
            if (typeof args === 'number') {
                isTime = true;
                break;
            }
        }
        if (!isTime) {
            window.requestAnimationFrame = polyfill;
        }
    });
}

EDIT: Oh, and @yckart - because opera's moved to webkit. Hard to believe they wouldn't just use -webkit- instead of -o-

webkitRequestAnimationFrame is broken in iOS if you try and use it on additional pages, you can reenable it by focusing on the url bar then back to the page. (tested on ios6.1 iPad3)
The code by KrofDrakula fixed the issue for me (although the bug he describes was in an iframe, my issue was with basic page-to-page navigation).
https://gist.github.com/KrofDrakula/5318048

A simpler fix for the problem mizzao pointed out: update lastTime after the timeout instead of immediately, so you never have to undo it. Also fixed to make sure request and cancelAnimationFrame are defined consistently, plus gratuitous style changes.

(function() {
    var vendors = ['webkit', 'moz'];
    for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
        var vp = vendors[i];
        window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
        window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
                                   || window[vp+'CancelRequestAnimationFrame']);
    }
    if (!window.requestAnimationFrame || !window.cancelAnimationFrame) {
        var lastTime = 0;
        window.requestAnimationFrame = function(callback) {
            var now = new Date().getTime();
            var nextTime = Math.max(lastTime + 16, now);
            return setTimeout(function() { callback(lastTime = nextTime); },
                              nextTime - now);
        };
        window.cancelAnimationFrame = clearTimeout;
    }
}());

A couple people have offered versions that collect multiple callbacks into a list all called for the same frame -- my code should handle the same usage without any extra fuss, assuming multiple setTimeouts with the same target time will get called without too much time in between. (I wonder if they will.)

Added cancellation support to @darius's mod

(function(window) {
    'use strict';

    var lastTime = 0,
        vendors = ['moz', 'webkit', 'o', 'ms'],
        x;

    // Remove vendor prefixing if prefixed and break early if not
    for (x = 0; x < vendors.length && !window.requestAnimationFrame; x += 1) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame']
                                   || window[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    // Check if full standard supported
    if (!window.cancelAnimationFrame) {
        // Check if standard partially supported
        if (!window.requestAnimationFrame) {
            // No support, emulate standard
            window.requestAnimationFrame = function(callback) {
                var now = new Date().getTime(),
                    nextTime = Math.max(lastTime + 16, now);

                return window.setTimeout(function() { callback(lastTime = nextTime); }, nextTime - now);
            };

            window.cancelAnimationFrame = window.clearTimeout;
        } else {
            // Emulate cancel for browsers that don't support it
            vendors = window.requestAnimationFrame;
            lastTime = {};

            window.requestAnimationFrame = function(callback) {
                var id = x; // Generate the id (x is initialized in the for loop above)
                x += 1;
                lastTime[id] = callback;

                // Call the vendors requestAnimationFrame implementation
                vendors(function(timestamp) {
                    if (lastTime.hasOwnProperty(id)) {
                        var error;
                        try {
                            lastTime[id](timestamp);
                        } catch (e) {
                            error = e;
                        } finally {
                            delete lastTime[id];
                            if (error) { throw error; }         // re-throw the error if an error occurred
                        }
                    }
                });

                // return the id for cancellation capabilities
                return id;
            };

            window.cancelAnimationFrame = function(id) {
                delete lastTime[id];
            };
        }
    }

}(this));

Just published an slightly modified version of this polyfill on bower and jamjs if you use those package managers: https://github.com/ngryman/raf.js.

I went through all the comments and revisions. Of all the iterations posted (or not), is there a best version of the polyfill to use as of July 12th 2013?

As @DanB-66 noted, requestAnimationFrame is completely borked in a variety of scenarios: iframes, page-to-page navigation, and, in our observation, in conjunction with infinitely running CSS animations.

CanIuse.com mentions that requestAnimationFrame has bugs. Other notable mentions:
http://shitwebkitdoes.tumblr.com/post/47186945856/native-requestanimationframe-broken-on-ios-6
http://forums.greensock.com/topic/6639-tweens-not-working-on-ios6/?p=24455#entry24455

The problem has not been fixed (thus far) in iOS7. IMO, this shim should detect iOS and simply not support it, for the time being.

I think it's time we move this to a repo. :D I won't be able to maintain it so I'm looking for some responsible people.

Also we need to address the iOS issues. I haven't researched them in-depth myself but we need to resolve them ASAP.

@KrofDrakula (his iOS-fix fork) or @darius .. either of you interested?

@paulirish I would be happy to maintain it; I'm about to release code at work (SproutSocial) that relies on a very similar polyfil)

very nice, thanks for sharing

i made two little modifications, instead of checking for the method to be falsy like this:

!window.requestAnimationFrame

i check for it to be explicitly undefined

window.requestAnimationFrame === undefined

and also when declaring vars i do it in a single var statement:

var lastTime = 0,vendors = ['ms', 'moz', 'webkit', 'o'],x;

@paulirish, I just made a repo at https://github.com/darius/requestAnimationFrame. It has KrofDrakula's test merged into my version above, though I haven't tested that on an iOS device yet.

  • How about:
var now = Date.now || function() {
        return +new Date
    }
  • And the long list of strings and attribute name is not friendly to compression,recommended to use the variable string concatenation if readability is in the extent permitted.

For example:

var _KEY_AnimationFrame = 'AnimationFrame',
    _KEY_requestAnimationFrame = 'request' + _KEY_AnimationFrame;
window[_KEY_requestAnimationFrame] = function(){/*............*/}
  • But as an shim, readability is consider can not do, need to be more concerned about is the compression ratio.

It is my fork: https://gist.github.com/Gaubee/6991570

FWIW, -o-requestAnimationFrame and -ms-requestAnimationFrame have never been used. Both browsers (IE 10+ and Opera 15+) use the unprefixed version. Firefox supports the unprefixed version since FF23, which was released last August (we are now 3 versions further).
So lines 10-15 can be rewritten to:

if (!window.requestAnimationFrame)
{
    window.requestAnimationFrame = window['webkitRequestAnimationFrame'];
    window.cancelAnimationFrame = window['webkitCancelAnimationFrame'] || window['webkitCancelRequestAnimationFrame'];
}

I did cleaned up the original a little bit:

// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel

// MIT license

(function (window, rAF, cAF) {
    var lastTime = 0, vendors = ['ms', 'moz', 'webkit', 'o'], x;

    for (x = 0; x < vendors.length && !window[rAF]; ++x) {
        window[rAF] = window[vendors[x] + 'RequestAnimationFrame'];
        window[cAF] = window[vendors[x] + 'CancelAnimationFrame']
            || window[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    if (!window[rAF]) {
        window[rAF] = function (callback) {
            var currTime = new Date().getTime(),
                timeToCall = Math.max(0, 16 - (currTime - lastTime)),
                id = window.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall);

            lastTime = currTime + timeToCall;

            return id;
        };
    }

    if (!window[cAF]) {
        window[cAF] = function (id) {
            window.clearTimeout(id);
        };
    }
}(this, 'requestAnimationFrame', 'cancelAnimationFrame'));

which compresses to 423 bytes:

!function(a,b,c){var f,d=0,e=["ms","moz","webkit","o"];for(f=0;f<e.length&&!a[b];++f)a[b]=a[e[f]+"RequestAnimationFrame"],a[c]=a[e[f]+"CancelAnimationFrame"]||a[e[f]+"CancelRequestAnimationFrame"];a[b]||(a[b]=function(b){var c=(new Date).getTime(),e=Math.max(0,16-(c-d)),f=a.setTimeout(function(){b(c+e)},e);return d=c+e,f}),a[c]||(a[c]=function(b){a.clearTimeout(b)})}(this,"requestAnimationFrame","cancelAnimationFrame");

@paulirish - Is this being hosted somewhere new now? There are links to various repos, but I couldn't tell which one of them is "officially sanctioned".

@knod - As far as I can tell, the requestAnimationFrame polyfill-repo from @darius is the most up-to-date one.

If you want sub-millisecond precision, you can try adding the performance.now polyfill from @timhall after @darius's one. @timhall also uses the more compatible shorthand notation instead of Date.now().

var hasPerformance = !!(window.performance && window.performance.now);

// Add new wrapper for browsers that don't have performance
if (!hasPerformance) {
    // Store reference to existing rAF and initial startTime
    var rAF = window.requestAnimationFrame,
        startTime = +new Date;

    // Override window rAF to include wrapped callback
    window.requestAnimationFrame = function (callback, element) {
        // Wrap the given callback to pass in performance timestamp
        var wrapped = function (timestamp) {
            // Get performance-style timestamp
            var performanceTimestamp = (timestamp < 1e12) 
                ? timestamp 
                : timestamp - startTime;

            return callback(performanceTimestamp);
        };

        // Call original rAF with wrapped callback
        rAF(wrapped, element);
    }        
}

Currently, I found these :

I can't tell which code is included in another one or not, and which is better... Maybe @kof has the answer ^^

I have created my library after reading all of this shims/polyfills. It includes all improvements and some additional features, like custom frame rate, feature detected raf (required in cases where raf is implemented but just doesn't work), using raf from the iframe, increased performance for multiple parallel animations when using non native version by grouping callback calls and using just one timer ...

https://github.com/kof/animation-frame

Thanks a lot for your quick answer, now I can move forward in my project with serenity ;)

I think that the approach of using new timer for each callback can cause unexpected behaviour.
Say you have several elements you want to hide,
if each hide() will be on its own timer callback, they might get hidden not on the same frame

You can overcome this using callback queue and single timer.
(its not the full polyfill, just the way of work)

(function (global) {
    var callbacksQueue = [];

    global.setInterval(function () {
        for (var i = 0; i < callbacksQueue.length; i++) {
            if (callbacksQueue[i] !== false) {
                callbacksQueue[i].call(null);
            }
        }

        callbacksQueue = [];
    }, 1000 / 60);

    global.requestAnimationFrame = function (callback) {
        return callbacksQueue.push(callback) - 1;
    }

    global.cancelAnimationFrame = function (id) {
        callbacksQueue[id] = false;
    }
}(window));

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.