Managing JavaScript on Resize

For the past good long while, I’ve been using a throttled function to do various JavaScript chores when the browser fires a resize event. It’s a good way to correct the heights of things or add a class when the window hits a certain size. That’s been fine for a while, but recently I found a need to upgrade things.

JSfiddle logo

I try to handle resize with CSS alone, but there are times when JavaScript needs to step in. For example, there might be a responsive navigation menu that needs to disappear or become a toggle in the mobile view. A resize function can adjust the height of stubborn elements or add classes to something as the browser crosses a media query breakpoint. It can also do nifty things like set the width of a canvas so that it’s always the same width as your header. I’m doing that at the time of this writing to keep Spider-Man from being scaled down in a smaller browser. And you can also do things like handle tab visibility, as I do in this fiddle to manage a canvas snow animation.

This is normally a two-step process. First I would load up a throttle/debounce function of some kind. Underscore has a pretty good one, but I normally just go with Ben Alman’s time-tested effort. Having a throttle function is important, because you don’t want your code running like mad whenever someone drags the window. Left unchecked, the browser will call your functions a stupid number of times and that can drag your site if you have a lot of stuff going on. I’ve found that 250 milliseconds is a pretty good interval.

Getting down to Business

Recently I needed to add something else to the manageResize function, but I couldn’t because that sucker gets established in my init function. I needed the ability to add more tasks later, so I rewrote it a bit and used JavaScript’s apply() method to make everything okay.

I’ll gloss over the major points here, but you can see a working example in this fiddle or by checking out the embed above. Oh, and we’re basing everything on a SITE object to keep our dirty little doings out of the global namespace.

SITE.resizeTasks : [];
// Setting up resize function to throttle.
$(window).resize( $.throttle( 250, SITE.manageResize ) );

In the snippet above, we’re initializing a tasks array and setting up the throttle. Our resize function will now fire no more than four times per second while the browser is being resized.

Our next snippet is from our site’s init function. We set up our general set of chores to be done whenever a resize occurs, and we fire it once just to make sure that we’re reacting properly to whatever size the user started with. Notice that initTasks isn’t a plain old function. It’s an object with two properties: a function and some arguments. This is important, because it sets us up to use apply() later.

var initTasks = {
    func: function(myMessage){
        SITE.counter ++;
        $('.feedback--resize').html(myMessage);
    },
    args : ["I'm resizing!"]
};
initTasks.func("I ran once during setup!");
SITE.addResizeTask(initTasks);

There’s one more thing we need to prepare before we just get to the manageResize function. We need to work out how we’re going to add new tasks. It’s not fancy. It mainly just pushes your task object into the tasks array, but before doing that it squares away the args property. If you don’t pass in any arguments, the property gets set to an empty array. This prevents it from being undefined when apply() is called.

SITE.addResizeTask = function(task) {
    console.log('adding task');
    task.args = task.args || [];
    SITE.resizeTasks.push(task);
};
SITE.manageResize = function() {
    for (var i = 0; i < SITE.resizeTasks.length; i++) {
        var task = SITE.resizeTasks[i];
        task.func.apply(this, task.args);
    };
};

In the manageResize function we loop through the tasks array (even though we currently just have one of them) and pass in the values for each object to apply(). The func property is the function and the args property is the arguments in the form of an array. The context isn’t important in this case, but if it matters to you then you can set some value for “this” before using apply().

Adding More Tasks

This setup allows us to add other tasks later, after the site’s init function has completed. You just need to make a new task object and toss it into addResizeTask().

var myTotallySeparateFunction = {
    func: function(myMessage){
        $('.feedback--other-resize').html(myMessage);
    },
    args : ["I'm also resizing!"]
};
SITE.addResizeTask(myTotallySeparateFunction);

Boom! All done! If your code is broken up into modules or separate files, you can still add resize tasks at any time from anywhere. There’s no need to cram all of your stuff into the init() or litter other handlers all over your codebase. And just for putting up with me today, here’s a photo of how this trick makes me feel.  🙂

Frolicking Pug
Source for image link: “Frolicking Pug” from Confessions of a Pugophile