JQuery:如何在调整大小完成后仅调用RESIZE事件一次?

110

当浏览器窗口调整大小完成后,如何调用函数?

我尝试使用以下方式实现,但遇到了问题。我正在使用JQuery的Resize事件函数:

$(window).resize(function() {
  ... // how to call only once the browser has FINISHED resizing?
});
然而,如果用户手动调整浏览器窗口大小,此函数会被连续调用。这意味着,在短时间内可能会调用该函数数十次。

我该如何在浏览器窗口完成调整大小后仅调用一次调整大小的函数?

更新

同时又不必使用全局变量。


@BGerrissen,如果您能向我展示如何在不使用全局变量的情况下完成http://jsfiddle.net/Zevan/c9UE5/1/,那我一定会感激不尽 :) - nickb
上述的clearTimeout/setTimeout方法非常有效。 - kris-o3
8个回答

129

这里是使用thejh的指示的示例

你可以存储任何 setInterval 或 setTimeout 的引用 id,像这样:

var loop = setInterval(func, 30);

// some time later clear the interval
clearInterval(loop);

如果我找到了不使用全局变量的方法,我会将其设为“被接受的答案”。 - nickb
4
我认为您错过了帖子末尾的链接……请查看:http://jsfiddle.net/Zevan/c9UE5/5/ - Zevan
@AlexFord 应用程序可能需要尽快了解当前大小,以防止窗口变得太大,或者出现任何其他情况... - wulftone
非常聪明 - Ahmed Mahmoud
如果你使用防抖/超时并保持调整大小不变(即不触发调整大小事件)超过防抖延迟时间,那么它会认为你已经完成并触发事件。我想我需要检查鼠标按钮的状态,而不是使用防抖:( - PJUK
显示剩余7条评论

89

去抖动.

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 
} ) );

请注意,您可以使用此方法来防抖任何您想要防抖的东西(例如键盘事件等)。

调整超时参数以获得最佳期望效果。


1
我刚试着运行了这个程序,但它似乎运行了两次。我用*"$("body").append("<br/>DONE!");"替换了"// do stuff"*,然后在浏览器调整大小时它会调用两次。 - nickb
3
使用内部this指针指向scope,并使用Array.prototype.slice.call(args)(将 args 转换为标准数组)作为参数来调用函数func - Eric
4
这是一个可重复使用的抽象,因此您不必手动编写超时或自己跟踪全局超时ID。您可以将其用于远比窗口调整更多的内容;例如,通过传递更高的超时参数来去抖动提交按钮。您可以选择更少的代码解决方案,但我建议您将这个片段放入工具包中,以后会感激不尽 ;) - BGerrissen
2
如果您已经在使用Underscore.js库,它有一个很好的实现。http://underscorejs.org/#debounce - skilleo
为什么你在闭包中没有保留对prototype.slice的引用呢?这虽然只是一个小的性能改进,但无论如何也是有益的。 http://jsfiddle.net/8zpKt/ - Denis
显示剩余11条评论

22
您可以将setTimeout()clearTimeout()jQuery.data结合使用:
$(window).resize(function() {
    clearTimeout($.data(this, 'resizeTimer'));
    $.data(this, 'resizeTimer', setTimeout(function() {
        //do something
        alert("Haven't resized in 200ms!");
    }, 200));
});

更新

我编写了一个扩展程序,以增强jQuery的默认on(和bind)事件处理程序。如果给定时间间隔内未触发事件,则它会为所选元素附加一个或多个事件处理程序函数。这在您想要延迟一段时间后再执行回调的情况下非常有用,例如调整大小事件等。 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));

像使用其他onbind事件处理程序一样使用它,除了您可以作为最后一个传递额外的参数:

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

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


1
每天都要做同样的谷歌搜索,然后找到这段代码的页面。真希望我能记住它哈哈。 - Dustin Silk

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);
});

我最喜欢的是这个,但为什么不在条件语句中使用花括号呢?我知道它们可以不用,但其他开发人员看起来会比较痛苦 ;) - teewuane

7

补充以上内容,由于滚动条的出现和消失通常会导致不必要的调整事件,因此可以使用以下代码来避免这种情况:

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
});

请查看jsfiddle

5

Underscore.js有几个非常好的方法可以完成此任务:throttledebounce。即使您不使用Underscore,请查看这些函数的源代码。以下是一个示例:

var redraw = function() {'redraw logic here'};
var debouncedRedraw = _.debounce(redraw, 750);
$(window).on('resize', debouncedRedraw);

2
这是我的方法:
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);
    });
});

resize-event-listener会捕获所有的调整大小事件,为每个调整大小事件创建一个超时函数,并将超时标识符与以'id-'为前缀的迭代数字一起保存在tos数组中(以便作为数组键使用)。

每次超时触发时,它都会调用fn函数,该函数检查是否是tos数组中的最后一个超时(fn函数删除每个已执行的超时)。如果为真(= if(len-1 == 0)),则调整大小完成。


如果您能在这里添加一些注释或解释,那就太好了。 - Brett Gregson

1
jQuery提供了一个off方法来移除事件处理程序。
$(window).resize(function(){
    if(magic == true) {
        $(window).off('resize', arguments.callee);
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接