JavaScript/JQuery: $(window).resize如何在调整大小完成后触发?

250
我正在使用 JQuery,如下所示:
$(window).resize(function() { ... });

然而,如果用户通过拖动窗口边缘手动调整浏览器窗口的大小,则上述.resize事件会多次触发。

问题:如何在浏览器窗口调整大小完成后调用函数(以便事件仅触发一次)?


请参考此答案:https://dev59.com/FXRB5IYBdhLWcg3wSVm1#668185。它涉及使用超时来延迟执行您的函数。 - Alastair Pitts
我不确定是否可能,但你可以尝试使用这个插件:http://benalman.com/projects/jquery-resize-plugin/ - BrunoLM
顺便提一下,我注意到这在移动浏览器上非常有用,因为当用户向下滚动时,地址栏往往会逐渐隐藏,从而调整屏幕大小。我还以为只有在桌面上才会发生屏幕调整大小... - MakisH
13个回答

314
这是对CMS解决方案的修改,可以在代码中的多个地方调用:
var waitForFinalEvent = (function () {
  var timers = {};
  return function (callback, ms, uniqueId) {
    if (!uniqueId) {
      uniqueId = "Don't call this twice without a uniqueId";
    }
    if (timers[uniqueId]) {
      clearTimeout (timers[uniqueId]);
    }
    timers[uniqueId] = setTimeout(callback, ms);
  };
})();

使用方法:

$(window).resize(function () {
    waitForFinalEvent(function(){
      alert('Resize...');
      //...
    }, 500, "some unique string");
});

如果您只调用一次CMS的解决方案,那么它是可以的,但是如果您多次调用它,例如,如果您的代码的不同部分设置了单独的回调以调整窗口大小,则它将失败,因为它们共享计时器变量。

通过这种修改,您可以为每个回调提供一个唯一的ID,并使用这些唯一的ID来保持所有超时事件分开。


2
我<3你,我在调整全屏(非HTML5)Highcharts图表大小时进行了组合,效果很棒。 - Michael J. Calkins
绝对令人难以置信! - Starkers
2
既然我从你的答案中受益匪浅,我想与社区的其他人分享一个可行的原型,它是基于你提供的代码构建的:https://jsfiddle.net/h6h2pzpu/3/。大家都来享受吧! - Jonas Stawski
1
这一切都很完美,但它会延迟实际函数的执行时间,以毫秒(ms)为单位,这可能非常不可取。 - Tomas M
这个和这个 - https://codepen.io/galingong/pen/qpzrbv - 是一样的,只是 timers 不是全局的,对吧? - galingong
显示剩余3条评论

141
我喜欢创建一个事件:
$(window).bind('resizeEnd', function() {
    //do something, window hasn't changed size in 500ms
});

以下是创建它的方法:

 $(window).resize(function() {
        if(this.resizeTO) clearTimeout(this.resizeTO);
        this.resizeTO = setTimeout(function() {
            $(this).trigger('resizeEnd');
        }, 500);
    });

你可以将这段代码放在一个全局的JavaScript文件中。

我使用了这个解决方案。但从长远来看,我想实现与brahn的解决方案相同。 - Bharat Patil
这使您可以在所有地方绑定该事件,而不仅仅是在一个函数中,假设您有多个页面/ .js文件需要对其进行单独的反应。 - hanzolo
这对我没有用,但DusanV的答案解决了问题。 - lightbyte

123

我使用以下函数来延迟重复的操作,它适用于您的情况:

var delay = (function(){
  var timer = 0;
  return function(callback, ms){
    clearTimeout (timer);
    timer = setTimeout(callback, ms);
  };
})();

使用方法:

$(window).resize(function() {
    delay(function(){
      alert('Resize...');
      //...
    }, 500);
});

传递给它的回调函数只会在经过指定时间后执行最后一次delay调用之后,否则计时器将被重置。我发现这对于其他用途非常有用,例如检测用户停止输入等...


6
如果多次使用这种方式(例如,如果代码的不同部分设置回调到$(window).resize),则我认为此方法可能会失败,因为它们将共享“timer”变量。请参见下面我的答案,以获取建议的解决方案。 - brahn
非常好!像魔法一样运作!喜欢你的回答……简单而优雅! - Mr. Pumpkin

74

如果您已安装Underscore.js,您可以:

$(window).resize(_.debounce(function(){
    alert("Resized");
},500));

1
即使您不想包含Underscore,至少也要获取此源代码:http://underscorejs.org/docs/underscore.html#section-67 - tybro0103
防抖是正确的技术,只是实现的问题。如果Underscore/Lodash已经是项目依赖的话,这是更优秀的实现方式。 - Factor Mystic
这是我现在正在使用的。 - Bharat Patil

24

之前提到的某些解决方案对我不起作用,尽管它们具有更广泛的使用。相反,我在窗口调整大小时找到了这个解决方案

$(window).bind('resize', function(e){
    window.resizeEvt;
    $(window).resize(function(){
        clearTimeout(window.resizeEvt);
        window.resizeEvt = setTimeout(function(){
        //code to do after window is resized
        }, 250);
    });
});

1
只有你的例子对我起作用了...上面的其他例子都没有!我不确定为什么你的答案没有被选中。 - Mumbo Jumbo
我不明白为什么要在调整大小事件内部捕获调整大小事件,但这对我也有效... - lightbyte
我认为这是因为其他人都专注于泛化,寻找任何此类问题的解决方案(例如函数的多次调用)。 - DusanV
lightbyte,这是因为每次调用函数时都必须停止先前的事件。 - DusanV

13

非常感谢 David Walsh,这里是一个纯净版的 underscore debounce 函数。

代码:

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

简单使用:

var myEfficientFn = debounce(function() {
    // All the taxing stuff you do
}, 250);

$(window).on('resize', myEfficientFn);

参考: http://davidwalsh.name/javascript-debounce-function


6

声明全局延迟监听器:

var resize_timeout;

$(window).on('resize orientationchange', function(){
    clearTimeout(resize_timeout);

    resize_timeout = setTimeout(function(){
        $(window).trigger('resized');
    }, 250);
});

下面使用监听器来处理resized事件,你可以根据自己的需要进行处理:

$(window).on('resized', function(){
    console.log('resized');
});

谢谢,那是目前最好的解决方案。我还测试过,在调整窗口大小时,250毫秒是最有效的方式。 - AlexioVay

6

谢谢@vishwajit76 - 多么好的解决方案。 - Ashley Frieze

6

实际上,据我所知,当调整大小关闭时,您确切地无法执行某些操作,这仅是因为您不知道未来用户的操作。但是,您可以假设两个调整大小事件之间经过的时间,因此,如果您等待的时间比此时间长一点,并且没有进行调整大小,则可以调用您的函数。
想法是我们使用setTimeout及其id来保存或删除它。例如,我们知道两个调整大小事件之间的时间为500ms,因此我们将等待750ms。

var a;
$(window).resize(function(){
  clearTimeout(a);
  a = setTimeout(function(){
    // call your function
  },750);
});


2

这是一个针对延迟窗口大小调整事件的简单 jQuery 插件。

语法:

为调整事件添加新函数

jQuery(window).resizeDelayed( func, delay, id ); // delay and id are optional

通过声明其ID,删除先前添加的功能

jQuery(window).resizeDelayed( false, id );

删除所有功能。
jQuery(window).resizeDelayed( false );

使用方法:

// ADD SOME FUNCTIONS TO RESIZE EVENT
jQuery(window).resizeDelayed( function(){ console.log( 'first event - should run after 0.4 seconds'); }, 400,  'id-first-event' );
jQuery(window).resizeDelayed( function(){ console.log('second event - should run after 1.5 seconds'); }, 1500, 'id-second-event' );
jQuery(window).resizeDelayed( function(){ console.log( 'third event - should run after 3.0 seconds'); }, 3000, 'id-third-event' );

// LETS DELETE THE SECOND ONE
jQuery(window).resizeDelayed( false, 'id-second-event' );

// LETS ADD ONE WITH AUTOGENERATED ID(THIS COULDNT BE DELETED LATER) AND DEFAULT TIMEOUT (500ms)
jQuery(window).resizeDelayed( function(){ console.log('newest event - should run after 0.5 second'); } );

// LETS CALL RESIZE EVENT MANUALLY MULTIPLE TIMES (OR YOU CAN RESIZE YOUR BROWSER WINDOW) TO SEE WHAT WILL HAPPEN
jQuery(window).resize().resize().resize().resize().resize().resize().resize();

使用输出:

first event - should run after 0.4 seconds
newest event - should run after 0.5 second
third event - should run after 3.0 seconds

插件:

jQuery.fn.resizeDelayed = (function(){

    // >>> THIS PART RUNS ONLY ONCE - RIGHT NOW

    var rd_funcs = [], rd_counter = 1, foreachResizeFunction = function( func ){ for( var index in rd_funcs ) { func(index); } };

    // REGISTER JQUERY RESIZE EVENT HANDLER
    jQuery(window).resize(function() {

        // SET/RESET TIMEOUT ON EACH REGISTERED FUNCTION
        foreachResizeFunction(function(index){

            // IF THIS FUNCTION IS MANUALLY DISABLED ( by calling jQuery(window).resizeDelayed(false, 'id') ),
            // THEN JUST CONTINUE TO NEXT ONE
            if( rd_funcs[index] === false )
                return; // CONTINUE;

            // IF setTimeout IS ALREADY SET, THAT MEANS THAT WE SHOULD RESET IT BECAUSE ITS CALLED BEFORE DURATION TIME EXPIRES
            if( rd_funcs[index].timeout !== false )
                clearTimeout( rd_funcs[index].timeout );

            // SET NEW TIMEOUT BY RESPECTING DURATION TIME
            rd_funcs[index].timeout = setTimeout( rd_funcs[index].func, rd_funcs[index].delay );

        });

    });

    // <<< THIS PART RUNS ONLY ONCE - RIGHT NOW

    // RETURN THE FUNCTION WHICH JQUERY SHOULD USE WHEN jQuery(window).resizeDelayed(...) IS CALLED
    return function( func_or_false, delay_or_id, id ){

        // FIRST PARAM SHOULD BE SET!
        if( typeof func_or_false == "undefined" ){

            console.log( 'jQuery(window).resizeDelayed(...) REQUIRES AT LEAST 1 PARAMETER!' );
            return this; // RETURN JQUERY OBJECT

        }

        // SHOULD WE DELETE THE EXISTING FUNCTION(S) INSTEAD OF CREATING A NEW ONE?
        if( func_or_false == false ){

            // DELETE ALL REGISTERED FUNCTIONS?
            if( typeof delay_or_id == "undefined" ){

                // CLEAR ALL setTimeout's FIRST
                foreachResizeFunction(function(index){

                    if( typeof rd_funcs[index] != "undefined" && rd_funcs[index].timeout !== false )
                        clearTimeout( rd_funcs[index].timeout );

                });

                rd_funcs = [];

                return this; // RETURN JQUERY OBJECT

            }
            // DELETE ONLY THE FUNCTION WITH SPECIFIC ID?
            else if( typeof rd_funcs[delay_or_id] != "undefined" ){

                // CLEAR setTimeout FIRST
                if( rd_funcs[delay_or_id].timeout !== false )
                    clearTimeout( rd_funcs[delay_or_id].timeout );

                rd_funcs[delay_or_id] = false;

                return this; // RETURN JQUERY OBJECT

            }

        }

        // NOW, FIRST PARAM MUST BE THE FUNCTION
        if( typeof func_or_false != "function" )
            return this; // RETURN JQUERY OBJECT

        // SET THE DEFAULT DELAY TIME IF ITS NOT ALREADY SET
        if( typeof delay_or_id == "undefined" || isNaN(delay_or_id) )
            delay_or_id = 500;

        // SET THE DEFAULT ID IF ITS NOT ALREADY SET
        if( typeof id == "undefined" )
            id = rd_counter;

        // ADD NEW FUNCTION TO RESIZE EVENT
        rd_funcs[id] = {
            func : func_or_false,
            delay: delay_or_id,
            timeout : false
        };

        rd_counter++;

        return this; // RETURN JQUERY OBJECT

    }

})();

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