110

How do I call a function once the browser windows has FINISHED resizing?

I'm trying to do it like so, but am having problems. I'm using the JQuery Resize event function:

$(window).resize(function() {
  ... // how to call only once the browser has FINISHED resizing?
});

However, this function is called continuously if the user is manually resizing the browser window. Which means, it might call this function dozens of times in short interval of time.

How can I call the resize function only a single time (once the browser window has finished resizing)?

UPDATE

Also without having to use a global variable.

nickb
  • 9,140
  • 11
  • 39
  • 48
  • @BGerrissen, if you can show me how to do http://jsfiddle.net/Zevan/c9UE5/1/ without a global variable, I definitely will :) – nickb Nov 28 '10 at 20:43
  • the above cleartimeout/settimeout method works wonderfully well. – kris-o3 Feb 27 '13 at 15:47
  • Possible duplicate of [JavaScript/JQuery: $(window).resize how to fire AFTER the resize is completed?](https://stackoverflow.com/questions/2854407/javascript-jquery-window-resize-how-to-fire-after-the-resize-is-completed) – EvilDr Jun 23 '17 at 08:00

8 Answers8

129

Here is an example using thejh's instructions

You can store a reference id to any setInterval or setTimeout. Like this:

var loop = setInterval(func, 30);

// some time later clear the interval
clearInterval(loop);
Zevan
  • 10,097
  • 3
  • 31
  • 48
  • I love that this code is so simply. Any way to make this work **without** having to use a global variable? – nickb Nov 28 '10 at 20:24
  • If I see how to do this without the global variable, I'll make that the "accepted answer" – nickb Nov 28 '10 at 20:45
  • no problem. There are a few ways. I've edited the answer to reflect the simplest one I could think of. – Zevan Nov 28 '10 at 20:49
  • thanks for the update but it appears to still use a global variable. Can you update it to not require a global variable (var id). Thanks – nickb Nov 28 '10 at 20:56
  • 4
    I think you missed the link at the end of the post... check it out: http://jsfiddle.net/Zevan/c9UE5/5/ – Zevan Nov 28 '10 at 21:09
  • Thanks for this. Too bad you have to do a horrible hack like this just to make sure resize runs once. Why would a browser make it run twice? – CatDadCode Jul 11 '12 at 22:16
  • just an fyi, id is now a 'global' as it is in the 'window scope' ('this' inside a resize event is 'window') try this instead: http://jsfiddle.net/5KpYs/2/ ​ – Jamie Pate Sep 19 '12 at 03:01
  • @AlexFord An application might need to know the current size as soon as possible, maybe to prevent the window from getting to big, or any number of other scenarios... – wulftone Jul 04 '13 at 23:32
  • Extremely intelligent – Ahmed Mahmoud Feb 26 '15 at 02:06
  • If you do it with a debounce/timeout and hold the resize in place (i.e. not triggering the resize event) for more than your debounce delay then it thinks you have finished and fires. I think I need to check the state of mouse button instead of using a debounce :( – PJUK May 18 '15 at 13:33
  • This is simply ingenious! Thanks @Zevan – AlbertSamuel Mar 23 '17 at 02:57
  • For some reason, this doesn't completely get rid of the problem for me in FF. Every time I resize, it fires the function an extra time. I have it as `jQuery( window ).resize function() { ... }).trigger( 'resize' );` and on load, the function fires once. After I resize a bunch, the function will fire twice. If I resize again, the function fires 3 times, etc. It has reduced the amount of firing significantly, but I was expecting it to only fire once. – gavsiu May 04 '17 at 05:28
89

Debounce.

function debouncer( func , timeout ) {
   var timeoutID , timeout = timeout || 200;
   return function () {
      var scope = this , args = arguments;
      clearTimeout( timeoutID );
      timeoutID = setTimeout( function () {
          func.apply( scope , Array.prototype.slice.call( args ) );
      } , timeout );
   }
}


$( window ).resize( debouncer( function ( e ) {
    // do stuff 
} ) );

Note, you can use this method for anything you want to debounce (key events etc).

Tweak the timeout parameter for optimal desired effect.

BGerrissen
  • 21,250
  • 3
  • 39
  • 40
  • 1
    I just tried running this and it appears to run **TWICE**. I replaced *"// do stuff"* with *"$("body").append("
    DONE!");"* and it calls it TWICE on a browser resize
    – nickb Nov 28 '10 at 19:50
  • oops... I forgot a rather important part (actually setting the timeoutID)... Fixed now, please do try again ;) – BGerrissen Nov 28 '10 at 19:52
  • Why is the debouncer calling .resize twice? Am I doing something wrong? Live example seen here --> http://jsfiddle.net/c9UE5/3/ – nickb Nov 28 '10 at 19:53
  • Works now :) (A lot more code than I was hoping for but it does work). – nickb Nov 28 '10 at 19:55
  • What does **func.apply(scope ,Array.prototype.slice.call(args));** do? – nickb Nov 28 '10 at 20:04
  • 3
    @user43493: It calls the cuntion `func`, with the internal `this` pointer pointing to `scope`, and with `Array.prototype.slice.call(args)` (which generates a standard array out of `args`) as the arguments – Eric Nov 28 '10 at 20:19
  • Why does this code seem so much more complicated than http://jsfiddle.net/Zevan/c9UE5/1/ I do like that no global variable is being used but it still seems way more complicated than the code I linked too. – nickb Nov 28 '10 at 20:23
  • 4
    This is a reusable abstraction, so you don't have to handcode timeouts or keep track of global timeoutID's yourself. You can use it for way more then just window resizing ;) for example to debounce a submit button by passing a higher timeout parameter. You can opt for the less code solution, but I do advice you keep this snippet in your kit, you will appreciate it later on ;) – BGerrissen Nov 28 '10 at 20:28
  • Make sense. Can you show the less code solution (that's more specific to this one particular use case)? – nickb Nov 28 '10 at 20:30
  • You linked that yourself.. http://jsfiddle.net/Zevan/c9UE5/1/ but you have to handcode that each time yourself for anything you want to debounce and then keep track of global variables so that your timeout ID doesn't get overwritten. – BGerrissen Nov 28 '10 at 20:33
  • Can you show me how to do http://jsfiddle.net/Zevan/c9UE5/1/ without having to use a global variable? – nickb Nov 28 '10 at 20:35
  • I'll make the simplified, no global variable response as my "accepted answer" :) – nickb Nov 28 '10 at 20:46
  • 2
    Underscore.js has a nice implementation of this if you're already using that lib. http://underscorejs.org/#debounce – skilleo Jun 19 '12 at 04:03
  • why you did not keep a reference to the prototype.slice in closure too? it's small performance improvement, but anyway.. http://jsfiddle.net/8zpKt/ – Denis Sep 20 '13 at 15:37
  • @Denis There's is no performance improvement in that, it would actually be a performance reduction (storing a needless reference). If you use .slice a lot in your code, it might be worthwhile to store a reference to reduce byte size, but it's still slower (insignificantly though) than calling it directly from the Array prototype. – BGerrissen Oct 28 '15 at 08:40
  • My new _most loved_ ;> for years was struggling with clear interval... – biesior Sep 03 '20 at 19:30
22

You can use setTimeout() and clearTimeout() in conjunction with jQuery.data:

$(window).resize(function() {
    clearTimeout($.data(this, 'resizeTimer'));
    $.data(this, 'resizeTimer', setTimeout(function() {
        //do something
        alert("Haven't resized in 200ms!");
    }, 200));
});

Update

I wrote an extension to enhance jQuery's default on (& bind)-event-handler. It attaches an event handler function for one or more events to the selected elements if the event was not triggered for a given interval. This is useful if you want to fire a callback only after a delay, like the resize event, or else. https://github.com/yckart/jquery.unevent.js

;(function ($) {
    var methods = { on: $.fn.on, bind: $.fn.bind };
    $.each(methods, function(k){
        $.fn[k] = function () {
            var args = [].slice.call(arguments),
                delay = args.pop(),
                fn = args.pop(),
                timer;

            args.push(function () {
                var self = this,
                    arg = arguments;
                clearTimeout(timer);
                timer = setTimeout(function(){
                    fn.apply(self, [].slice.call(arg));
                }, delay);
            });

            return methods[k].apply(this, isNaN(delay) ? arguments : args);
        };
    });
}(jQuery));

Use it like any other on or bind-event handler, except that you can pass an extra parameter as a last:

$(window).on('resize', function(e) {
    console.log(e.type + '-event was 200ms not triggered');
}, 200);

http://jsfiddle.net/ARTsinn/EqqHx/

yckart
  • 32,460
  • 9
  • 122
  • 129
  • 1
    Literally do the same google search every day. and come to the same page for this piece of code. Wish i could just remember it haha. – Dustin Silk Nov 07 '14 at 10:07
7

Just to add to the above, it is common to get unwanted resize events because of scroll bars popping in and out, here is some code to avoid that:

function registerResize(f) {
    $(window).resize(function() {
        clearTimeout(this.resizeTimeout);
        this.resizeTimeout = setTimeout(function() {
            var oldOverflow = document.body.style.overflow;
            document.body.style.overflow = "hidden";
            var currHeight = $(window).height(),
                currWidth = $(window).width();
            document.body.style.overflow = oldOverflow;

            var prevUndefined = (typeof this.prevHeight === 'undefined' || typeof this.prevWidth === 'undefined');
            if (prevUndefined || this.prevHeight !== currHeight || this.prevWidth !== currWidth) {
                //console.log('Window size ' + (prevUndefined ? '' : this.prevHeight + "," + this.prevWidth) + " -> " + currHeight + "," + currWidth);
                this.prevHeight = currHeight;
                this.prevWidth = currWidth;

                f(currHeight, currWidth);
            }
        }, 200);
    });
    $(window).resize(); // initialize
}

registerResize(function(height, width) {
    // this will be called only once per resize regardless of scrollbars changes
});

see jsfiddle

kofifus
  • 17,260
  • 17
  • 99
  • 173
7
var lightbox_resize = false;
$(window).resize(function() {
    console.log(true);
    if (lightbox_resize)
        clearTimeout(lightbox_resize);
    lightbox_resize = setTimeout(function() {
        console.log('resize');
    }, 500);
});
Jlis
  • 71
  • 1
  • 1
  • I like this the most but why not use curly brackets on your conditional statements? I know it works without them but it's a pain for other devs to look at ;) – teewuane Feb 20 '14 at 18:55
5

Underscore.js has a couple of great methods for this task: throttle and debounce. Even if you're not using Underscore, take a look at the source of these functions. Here's an example:

var redraw = function() {'redraw logic here'};
var debouncedRedraw = _.debounce(redraw, 750);
$(window).on('resize', debouncedRedraw);
tybro0103
  • 48,327
  • 33
  • 144
  • 170
2

This is my approach:

document.addEventListener('DOMContentLoaded', function(){
    var tos = {};
    var idi = 0;
    var fn  = function(id)
    {
        var len = Object.keys(tos).length;

        if(len == 0)
            return;

        to = tos[id];
        delete tos[id];

        if(len-1 == 0)
            console.log('Resize finished trigger');
    };

    window.addEventListener('resize', function(){
        idi++;
        var id = 'id-'+idi;
        tos[id] = window.setTimeout(function(){fn(id)}, 500);
    });
});

The resize-event-listener catches all incoming resize calls, creates a timeout-function for each and saves the timeout-identifier along with an iterating number prepended by 'id-' (to be usable as array key) in the tos-array.

each time, the timout triggers, it calls the fn-function, that checks, if that was the last timeout in the tos array (the fn-function deletes every executed timout). if true (= if(len-1 == 0)), the resizing is finished.

lsblsb
  • 1,292
  • 12
  • 19
1

jQuery provides an off method to remove event handler

$(window).resize(function(){
    if(magic == true) {
        $(window).off('resize', arguments.callee);
    }
});
Qiang
  • 1,468
  • 15
  • 18