On Event Throttling

The Problem

Recently I had to use the window object’s onresize event in a project I was working on. I needed it in order to reposition some elements on a page whenever the browser’s window size changed. The trouble was, rx the function was relatively intense and was causing IE to choke. The reason? Different browsers fire the event at different intervals. In Firefox, decease it fires only once the user stops resizing the window. In IE, generic it fires continuously while the user is resizing the window. This of course, meant that IE was trying to run my function once every millisecond or so.

window.onresize = function () {
// Do something, just nothing intense for IE's sake
}

The Solution

The solution is event throttling, something that Christian Heilmann had mentioned to me once before. Here’s how my flavour of it works:

var timeoutId;

/**
* Throttles a resize event.
* @param {Function} fn  The function to execute
*/
function throttleResize(fn) {
window.onresize = function () {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(
function () {
fn.call();
},
250);
};
}

/**
* Just a function to fire on resize
*/
function doSomething() {
alert("I've been resized!");
}

/**
* Set up the throttle
*/
throttleResize(doSomething);

As you can see, I’m setting the onresize event for the window object as you normally would. The difference is, I’m only calling my doSomething function after a 250 millisecond delay. The way I do this is to simply use the window object’s setTimeout method, with a catch. The trick is to clear the setTimeout method I’d previously set before setting it again. This effectively resets the timer every time the onresize event fires until it’s done firing.

Getting Fancy

The continuous triggering of the onresize seems to be limited to IE, though I think I saw it in Safari 3 Beta for Windows as well. At any rate, this can be managed with an if/else block that checks to see which browser is running the code and either wrapping the function with the timer or not. I’ve found YUI‘s YAHOO.env.ua works quite well at identifying the browser. But for simplicity’s sake, I’ll manually verify if we’re in Firefox or not for this example. Here’s the same code with the differentiation applied:

var timeoutId;

/**
* Throttles a resize event.
* @param {Function} fn  The function to execute
*/
function throttleResize(fn) {
if (navigator.userAgent.indexOf("Gecko") === -1) {
window.onresize = function () {
window.clearTimeout(window.timeoutId);
window.timeoutId = window.setTimeout(
function () {
fn.call();
},
250);
};
} else {
window.onresize = function () {
fn.call();
};
}
}

/**
* Just a function to fire on resize
*/
function doSomething() {
alert("I've been resized!");
}

/**
* Set up the throttle
*/
throttleResize(doSomething);

It’s important to note that if your onresize event handler modifies the height of the body element, you’ll be inadvertently re-triggering the onresize event in IE. You can read more about this phenomenon and how to deal with it in Jonathan Snook’s post, IE Fires Onresize When Body Resizes.

Tags: