June 1, 2011 12

A better setTimeout()/setInterval()

By

Update (2011/07/09)
Firefox 5 implements mozRequestAnimationFrame but not mozCancelRequestAnimationFrame. To ensure the shims work as expected the code has been tweaked to force Firefox to use setTimeout/setInterval until mozCancelRequestAnimationFrame is available. Download from Gist

Update (2011/06/04)
Modified the functions so that they can be cancelled with drop in replace functions for clearTimeout() & clearInterval(). The modifications are up on Gist. Thanks to Luke Wilson for the suggestion!


The CSS Animations keyframe events shim that I recently posted makes use of a new experimental Javascript function requestAnimationFrame(). This function is designed to produce much higher performance than the traditional alternatives setTimeout() or setInterval().

This is because browser developers have worked hard to enable acurate 60fps performance from requestAnimationFrame(), Paul Irish explains it best on his blog:

The browser can optimize concurrent animations together into a single reflow and repaint cycle, leading to higher fidelity animation. For example, JS-based animations synchronized with CSS transitions.

This is great but you often want to receive callbacks at frequencies other that 60fps, especially when you’re using timing functions for something other than animation. Both setTimeout() and setInterval() allow for an arbitrary time in milliseconds to be provided, e.g.

setTimeout(function(){ alert("hello world") }, 1000)

The above would delay the function call by 1000 milliseconds (1 second).

So we want the performance gains of requestAnimationFrame() but the flexibility, familiarity and ease of use of setTimeOut() & setInterval().

This can be easily achieved using a couple of shim functions that I’ve called requestTimeout() and requestInterval() which I’ve outlined below. They are also available on Gist if you prefer.

requestTimeout(fn, delay) Fork on Gist →

window.requestTimeout = function(fn, delay) {
    if( !window.requestAnimationFrame       && 
        !window.webkitRequestAnimationFrame && 
        !window.mozRequestAnimationFrame    && 
        !window.oRequestAnimationFrame      && 
        !window.msRequestAnimationFrame)
            return window.setTimeout(fn, delay);

    var start = new Date().getTime(),
        handle = new Object();

    function loop(){
        var current = new Date().getTime(),
        delta = current - start;

        delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
    };

    handle.value = requestAnimFrame(loop);
    return handle;
};

window.clearRequestTimeout = function(handle) {
    window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
    window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value)   :
    window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
    window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
    window.msCancelRequestAnimationFrame ? msCancelRequestAnimationFrame(handle.value) :
    clearTimeout(handle);
};

requestInterval(fn, delay) Fork on Gist →

window.requestInterval = function(fn, delay) {
    if( !window.requestAnimationFrame       && 
        !window.webkitRequestAnimationFrame && 
        !window.mozRequestAnimationFrame    && 
        !window.oRequestAnimationFrame      && 
        !window.msRequestAnimationFrame)
            return window.setInterval(fn, delay);

    var start = new Date().getTime(),
    handle = new Object();

    function loop() {
        var current = new Date().getTime(),
        delta = current - start;

        if(delta >= delay) {
            fn.call();
            start = new Date().getTime();
        }

        handle.value = requestAnimFrame(loop);
    };

    handle.value = requestAnimFrame(loop);
    return handle;
}

window.clearRequestInterval = function(handle) {
    window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
    window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value)   :
    window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
    window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
    window.msCancelRequestAnimationFrame ? msCancelRequestAnimationFrame(handle.value) :
    clearInterval(handle);
};

Both of these functions require Paul Irish’s requestAnimFrame() shim which can be found on his blog. If a high performance function isn’t found then both functions will fall back to the native counterparts instead of making use of Paul’s shim.

  • Pingback: requestAnimationFrame for smart animating « Paul Irish

  • Akkumulator

    Hi, clearRequestInterval does not work in my javascript code. I have this code:        interval = requestInterval(Loop,100); clearRequestInterval(interval);

    The problem is, that Loop is still calles after clearRequestInterval(interval);

  • Dennis

    Hi,

    I am experiencing the same problem in Firefox 5- I noticed that when calling the requestInterval function, the handle.value that it returns is undefined (which is actually returned by the mozRequestAnimationFrame function) and calling the clearRequestInterval to that handle doesn’t work. Any idea why this is happening?

  • http://www.joelambert.co.uk Joe Lambert

    It appears that Firefox 5 only supports mozRequestAnimationFrame and not the mozCancelRequestAnimationFrame counterpart needed to clear animation requests.

    I’ll check to see if/when this is going to be fixed. However in the short term I’d consider Firefox’s implementation of this to be broken!

    I have a small fix that I’ll push up when I get some decent net access that causes Firefox5 to fallback to setInterval until mozCancelRequestAnimationFrame is available.

  • http://www.joelambert.co.uk Joe Lambert

    The modified code is now available from the Gist: https://gist.github.com/1002116

    It also appears that this is a known Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=647518

  • Dennis

    Thank you for sharing your work – once they fix this Firefox bug, I think this replacement you have created will be very helpful to many of us :)

  • Pingback: La boucle principale avec requestAnimationFrame

  • Pingback: HTML5 Canvas Performance and Optimization Tips, Tricks and Coding Best Practices | Build Future Repository

  • http://www.facebook.com/Ninja.Pipes Ash Blue

    Any reason why this code wouldn’t be stable in the latest Firefox? Currently having issues with clear animation intervals. It seems to be sporadic with acknowledging to clear them or not.

  • Glidias

    Take note that this code will use a global “handle” variable. So, make sure you aren’t using it in global namespace, or have to replace it with another name.

     For window.requestInterval’s loop() function, you made a mistake, which would cause clearRequestInterval() to not work if it’s executed within the fn.call() invocation.

    It might be better if fn.call() is called AFTER the requestAnimFrame(), not before it. This would allow situations within fn.call() to cancel the latest requested animation frame, and not end up canceling an outdated animation frame.

    Here it is edited function:

    function loop() {     handle.value = requestAnimFrame(loop);     var current = new Date().getTime(),     delta = current – start;

        if(delta >= delay) {         fn.call();         start = new Date().getTime();     }     }; handle.value = requestAnimFrame(loop); return handle; }

  • Pete Otaqui
  • http://WWW.AMARILLASINTERNET.TZ.TO CARLOS POLONIO

    ♠♠22