JavaScript窗口调整大小事件

737

如何监听浏览器窗口缩放事件?

虽然有一种使用jQuery的方法监听resize事件,但我不想因为这一个需求而引入jQuery到我的项目中。


9
谢谢大家。我不关心IE浏览器,我更加关注在手机Opera浏览器上的调整大小问题。 - Dead account
12个回答

794

最佳实践是添加到resize事件,而不是替换它:

window.addEventListener('resize', function(event) {
    ...
}, true);

另一个选择是为DOM事件创建一个单一的处理程序(但只能有一个),例如:

window.onresize = function(event) {
    ...
};

jQuery 可能会做一些工作来确保在所有浏览器中一致地触发resize事件,但我不确定是否有任何浏览器存在差异,建议您在Firefox、Safari和IE中进行测试。


240
这种方法的一个潜在问题是,你正在覆盖事件,因此无法将多个操作附加到一个事件上。 addEventListener/attachEvent组合是最好的选择,可以使你的JavaScript与页面上执行的任何其他脚本兼容。 - MyItchyChin
4
var onresize = window.onresize; window.onresize = function(event) { if (typeof onresize === 'function') onresize(); /** ... */ }翻译:将窗口大小调整事件绑定至变量onresize,然后创建一个新的窗口大小调整事件处理函数。在该处理函数中,如果变量onresize是一个函数,则调用它。注意,还有其他代码(被省略部分)在该处理函数中未列出。 - SubOne
249
window.addEventListener('resize', function(){}, true); - WebWanderer
19
@SubOne是一个如何不应该做的完美例子。 - Eugene Pankov
36
ECMAScript 6:window.onresize = () => { /* code */ }; 或者更好的写法是:window.addEventListener('resize', () => { /* code */ }); - Derk Jan Speelman
显示剩余11条评论

635

首先,我知道在上面的评论中提到了addEventListener方法,但我没有看到任何代码。由于这是首选方法,所以在此提供:

window.addEventListener('resize', function(event){
  // do stuff here
});

这里有一个可运行的示例


70
这是最直接的回答。 - jacoviza
7
听起来这是最佳答案,因为它不会覆盖 window.onresize 事件 :-) - James Harrington
34
许多人像这个例子中一样添加匿名事件监听器,但这并不是一个好的实践方式,因为这种方法使得以后无法删除这些监听器。虽然过去网页的生命周期很短,所以这并不是问题,但在如今的 AJAX、RIA和SPA时代,这变成了一个问题。我们需要一种管理事件监听器生命周期的方法。因此,最好将监听器函数分解为单独的函数,以便稍后可以用 removeEventListener 来删除它。 - Stijn de Witt
9
为什么不能像这样简洁明了地回答问题,不要说废话。 - quemeful
2
我认为Stijn de Witt和quemeful的回答都有一定的道理。有时候你关心移除事件监听器,但在你不需要的情况下,像上面的例子中添加所有额外的代码是不必要的,会导致代码膨胀和可读性降低。在我看来,如果你不预见需要移除监听器的原因,这种方法是最好的解决方案。如果有必要,你总是可以稍后重写它。根据我的经验,这些需求只会在特定情况下出现。 - cazort
显示剩余2条评论

545

不要覆盖window.onresize函数。

相反,创建一个函数来向对象或元素添加事件监听器。如果监听器不起作用,则将覆盖对象的函数作为最后的手段。这是jQuery等库中使用的首选方法。

object:元素或窗口对象
type:resize,scroll(事件类型)
callback:函数引用

var addEvent = function(object, type, callback) {
    if (object == null || typeof(object) == 'undefined') return;
    if (object.addEventListener) {
        object.addEventListener(type, callback, false);
    } else if (object.attachEvent) {
        object.attachEvent("on" + type, callback);
    } else {
        object["on"+type] = callback;
    }
};

然后像这样使用:

addEvent(window, "resize", function_reference);

或使用匿名函数:

addEvent(window, "resize", function(event) {
  console.log('resized');
});

108
这应该是被接受的答案。覆盖onresize只是一个不好的做法。 - oxygen
10
哈哈,这些额外的空值检查是怎么回事?在尝试在IE 4.5中工作吗? - FlavorScape
2
你可能在想,如何调用这个函数以接收窗口大小调整事件呢?方法如下:addEvent(window, "resize", function_reference); :) - dev4life
6
请注意,从IE 11开始,不再支持attachEvent。未来的首选方式是addEventListener - Luke
6
注意 elem == undefined。虽然不太可能,但有可能“undefined”在本地被定义为其他值。参考链接:https://dev59.com/u13Ua4cB1Zd3GeqP_lzg - Luke
显示剩余12条评论

63

2018年及以后的解决方案:

您应该使用 ResizeObserver 。 它是一个浏览器原生解决方案,比使用resize事件性能更好。 此外,它不仅支持在document上的事件,还支持在任意元素上。

var ro = new ResizeObserver( entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;
    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);
目前,Firefox、Chrome、Safari 和 Edge 支持 Resize Observer。对于其他(以及较老的)浏览器,您需要使用polyfill来实现相同的功能。

2
请问“更好的性能”这个说法有任何来源吗?(我不是在反驳,只是想问一下 :))我知道window:resize会频繁触发,但对于一个简单的“如果这样就那样”的方法,它不应该拖慢应用程序,你认为呢? - Tony
根据MDN的说明,它只能在SVG元素上使用。 - chovy
使用addEventListener时,您只需关注您感兴趣的元素;而使用ResizeObserver时,您需要关注所有元素,因此我想知道它如何更高效。此外,如果您只关注一个元素,使用addEventListener似乎更易读,而不是循环并找到正确的元素(是否应该有一个id)。我是否遗漏了什么? - undefined
@chovy 这不是真的 => MDN文档中指出:"ResizeObserver接口报告了元素内容或边框框的尺寸变化,或者是SVG元素的边界框的变化。" - undefined
我没有在这里回复。 - undefined

53

由于 resize 事件在调整大小时会不断触发,因此永远不应直接使用该事件。

请使用防抖函数来减少过多的调用。

window.addEventListener('resize',debounce(handler, delay, immediate),false);

这里是一个常见的 debounce 函数,在 lodash 中可能有更高级的版本。

const debounce = (func, wait, immediate) => {
    var timeout;
    return () => {
        const context = this, args = arguments;
        const later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        const callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

这可以像这样使用...

window.addEventListener('resize', debounce(() => console.log('hello'),
200, false), false);

它永远不会以每200毫秒以上的速率触发。

对于移动设备方向更改,请使用:

window.addEventListener('orientationchange', () => console.log('hello'), false);

这是我整理的一个小型库,能够很好地处理此类问题。


我很感激以下内容的信息:debounce(尽管我之前一直抵制以这种方式使用超时,但现在看来它是一个非常有价值的概念/函数),lodash(虽然我个人并不完全认同)以及解决关于是否将addEvent作为addEventListener的备选项的歧义(只是因为旧答案没有明确回应关于此事的评论)。 - wunth
9
请注意,由于您使用了ES6箭头函数符号,内部函数需要有(...arguments) => {...}(或者为避免混淆可以使用...args)作为参数,因为arguments变量不再在其作用域中可用。 - coderatchet
我完全明白,那段代码片段并不是我的代码,它只是一个例子。 - frontsideup
2
很好的回答,需要解决resize的性能问题!防抖是一种选择,另一种选择是简单的节流(如果您需要UI以“步骤”方式调整大小,则可能是正确的方法)。请参见http://bencentra.com/code/2015/02/27/optimizing-window-resize.html以获取示例。 - Robin Métral
orientationchange已被弃用。 - Lukas

17

我相信@Alex V已经提供了正确的答案,但这个答案需要一些现代化,因为它已经超过五年了。

主要有两个问题:

  1. 不要使用object作为参数名,它是一个保留字。因此,在strict mode中,@Alex V提供的函数将无法工作。

  2. @Alex V提供的addEvent函数如果使用addEventListener方法,则不会返回event object。应该添加另一个参数到addEvent函数中以支持这个功能。

注意:新参数被设置为可选,以便迁移到这个新版本函数不会破坏对此函数的任何先前调用。所有旧用途都将得到支持。

以下是更新后的addEvent函数:

/*
    function: addEvent

    @param: obj         (Object)(Required)

        -   The object which you wish
            to attach your event to.

    @param: type        (String)(Required)

        -   The type of event you
            wish to establish.

    @param: callback    (Function)(Required)

        -   The method you wish
            to be called by your
            event listener.

    @param: eventReturn (Boolean)(Optional)

        -   Whether you want the
            event object returned
            to your callback method.
*/
var addEvent = function(obj, type, callback, eventReturn)
{
    if(obj == null || typeof obj === 'undefined')
        return;

    if(obj.addEventListener)
        obj.addEventListener(type, callback, eventReturn ? true : false);
    else if(obj.attachEvent)
        obj.attachEvent("on" + type, callback);
    else
        obj["on" + type] = callback;
};

使用新的addEvent函数的示例调用:

var watch = function(evt)
{
    /*
        Older browser versions may return evt.srcElement
        Newer browser versions should return evt.currentTarget
    */
    var dimensions = {
        height: (evt.srcElement || evt.currentTarget).innerHeight,
        width: (evt.srcElement || evt.currentTarget).innerWidth
    };
};

addEvent(window, 'resize', watch, true);

我认为在2017年,attachEvent已经不再相关。 - Vlad Nicula
@VladNicula 我同意,但这个答案已经两年了。 - WebWanderer

6
window.onresize = function() {
    // your code
};

25
正如上面许多评论所说,最好不要覆盖onresize函数,而是添加一个新事件。请参阅Jondlm的答案。 - Luke

5
以下博客文章可能对您有用:修复IE中的窗口调整大小事件 它提供了以下代码:
Sys.Application.add_load(function(sender, args) {
    $addHandler(window, 'resize', window_resize);
});

var resizeTimeoutId;

function window_resize(e) {
     window.clearTimeout(resizeTimeoutId);
     resizeTimeoutId = window.setTimeout('doResizeCode();', 10);
}

6
仅适用于ASP.NET应用程序。 - Evgeny Gorb

3

如果您只想调整窗口大小,则上面提到的解决方案将起作用。但是,如果您希望将调整大小传播到子元素,则需要自己传播事件。以下是一些示例代码:

window.addEventListener("resize", function () {
  var recResizeElement = function (root) {
    Array.prototype.forEach.call(root.childNodes, function (el) {

      var resizeEvent = document.createEvent("HTMLEvents");
      resizeEvent.initEvent("resize", false, true);
      var propagate = el.dispatchEvent(resizeEvent);

      if (propagate)
        recResizeElement(el);
    });
  };
  recResizeElement(document.body);
});

请注意,子元素可以调用。
 event.preventDefault();

在传递给resize事件的第一个参数中,可以使用事件对象。例如:

var child1 = document.getElementById("child1");
child1.addEventListener("resize", function (event) {
  ...
  event.preventDefault();
});

2
您可以采用以下方法,对于小型项目来说是可行的。 参考链接
<body onresize="yourHandler(event)">

function yourHandler(e) {
  console.log('Resized:', e.target.innerWidth)
}
<body onresize="yourHandler(event)">
  Content... (resize browser to see)
</body>


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