Julian Jelfs’ Blog

jQuery plug-in for deferred resize (or anything)

Posted in Javascript, jQuery by julianjelfs on May 7, 2010

There are a class of UI events that fire many many times in quick succession. Things like mouse move, resize or even keyup. Often (in my experience) you might want to perform some action after the event has stopped happening rather than for each and every event. Suppose you want to do some expensive re-organisation of the UI in response to the resizing of some container element then you most likely don’t want to do this in response to every resize event, rather you want to wait until it appears that the user has stopped resizing and then perform the action. I have found it useful to encapsulate this pattern as a jQuery plug-in so that instead of this:

element.resize(function(){
    //do something expensive
});

I can do this:

element.deferredResize(function(){
    //do something expensive
}, 300);

And the plug-in looks like this:

$.fn.extend({    
    deferredResize : function(fn, delay){
        var timer = null;
        $(this).resize(function(){
            if(timer != null){
                clearTimeout(timer);
                timer = null;
            }
            timer = setTimeout(fn, delay);
        });
        return this;
    }
});

So when it picks up a resize event it will wait 300 milliseconds before kicking of the handler. In the interim, if it receives another resize event it will cancel the first timer and start the wait again. The end result is that you can achieve a nice balance between performance and responsiveness.

You could refactor this as a general extension to $.bind I guess but I think it is less useful for other events.

Advertisements

4 Responses

Subscribe to comments with RSS.

  1. Tony said, on December 25, 2011 at 9:00 pm

    Julian

    This is great – many thanks. I’ve tried to extend it as bit to support passing in data (like to resize() or bind()). Unfortunately I cannot get it to work; when the callback fn is called, the event does not have anything in the ‘data’ field.

    $.fn.extend({
    deferredResize : function(fn, delay, extras){
    var timer = null;
    $(this).resize(extras || {}, function(e){
    if(timer !== null){
    clearTimeout(timer);
    timer = null;
    }
    timer = setTimeout(function(){fn(e);}, delay);
    });
    return this;
    }
    });

    Appreciate any hints.
    Thanks

    • julianjelfs said, on December 25, 2011 at 9:27 pm

      Hi,

      I’ve made a few modifications to it since I posted this to allow you to pass in whether you want to track X or Y resizing or both and also what the delay should be. You should be able to tag on passing in extra data as well like I have done below. Notice that you don’t actually need to pass the data into the jQuery resize function because you still have access the data at the time that you want to call the callback anyway. Does that work for you?

      deferredResize : function(fn, options){
                  options = $.extend({x:true,y:true, delay:300, data : {}}, options); 
                  var timer = null;
                  var element = $(this);
                  var w = element.width();
                  var h = element.height();
                  element.resize(function(e){
                      if(timer != null){
                          clearTimeout(timer);
                          timer = null;
                      }
                      timer = setTimeout(function(){
                          var neww = element.width();
                          var newh = element.height();
                          
                          if(newh == h && neww == w) //hasn't really resized
                              return;
                              
                          if(!options.x && newh == h) //not tracking width change
                              return;
                             
                          if(!options.y && neww == w) //not tracking height change
                              return;
                                  
                          fn({height:newh, width:neww, data : options.data});
                          
                          h = newh;
                          w = neww;
                      
                      }, options.delay);
                  });
                  return this;
              }
      
      • Tony said, on December 26, 2011 at 11:08 am

        Julian

        Thanks for the more sophisticated rework – I’ll try it soon as it looks useful (I only care about vertical resizes at the moment). I had wanted to use the same mechanism to pass additional data as jQuery uses, but I just cannot get it to work.

        Have you thought how you can unbind these deferred events?

        Thanks

        T

      • julianjelfs said, on December 30, 2011 at 7:39 am

        I personally have not had any need for unbinding the resize so I never implemented it. If I was going to I would probably either store the function I was binding to the element on the element itself using element.data() so I could later unbind that specific function or use namespaced events so that I could unbind without affecting anything else. I think either way should work ok.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: