Dominic Szablewski, @phoboslab
— Sunday, May 6th 2012

What the requestAnimationFrame API Should Have Looked Like

Chrome, Firefox and IE10 now support requestAnimationFrame – a noble idea, with a totally backwards API.

Imagine you have this JavaScript animation that you want to constantly update. An animation that constantly updates, needs to be updated more than once, i.e. constantly. After all it's an animation. So why on earth do I have an API to request one update at a time?

Mozilla's documentation even warns developers that the API might not work as expected:

Note: Your callback routine must itself call requestAnimationFrame() unless you want the animation to stop.

Ah, yes.

Car analogy time: I have this very nice car that makes a beeping noise whenever I open the door with the ignition off and the headlights still on. You know, to warn me that my headlights might run the battery dry in my absence. "Thank you car, that's very kind of you. But let me ask you something: why didn't you just turn the headlights off yourself instead of notifying me?"

The requestAnimationFrame API is modeled after setTimeout when it should've been modeled after setInterval instead.

Here's my proposal:

// Install an animation that continously calls the callback
var animationId = setAnimation( callback, element );

// Stop the animation
clearAnimation( animationId );

And the code to make this happen:

(function(w) { 
    "use strict";

    // Find vendor prefix, if any
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for( var i = 0; i < vendors.length && !w.requestAnimationFrame; i++ ) {
        w.requestAnimationFrame = w[vendors[i]+'RequestAnimationFrame'];
    }

    // Use requestAnimationFrame if available
    if( w.requestAnimationFrame ) {
        var next = 1, 
            anims = {};

        w.setAnimation = function( callback, element ) {
            var current = next++;
            anims[current] = true;

            var animate = function() {
                if( !anims[current] ) { return; } // deleted?
                w.requestAnimationFrame( animate, element );
                callback();
            };
            w.requestAnimationFrame( animate, element );
            return current;
        };

        w.clearAnimation = function( id ) {
            delete anims[id];
        };
    }

    // [set/clear]Interval fallback
    else {
        w.setAnimation = function( callback, element ) {
            return w.setInterval( callback, 1000/60 );
        }
        w.clearAnimation = w.clearInterval;
    }
}(window));

Gist on Github

© 2024 Dominic Szablewski – Imprint – powered by Pagenode (5ms) – made with <3