在JavaScript中捕捉浏览器的“缩放”事件

197

是否可以使用JavaScript检测用户在页面中更改缩放比例?我想捕获“缩放”事件并对其做出响应(类似于window.onresize事件)。

谢谢。


16
我相信较新的浏览器在缩放页面时会触发onresize事件。 - epascarello
1
我有一个类似的问题,但我不仅想知道何时更改缩放比例,我还想在页面加载时知道缩放比例的值。 - Nosredna
3
确实不是重复问题。这个问题是关于捕捉事件,而不是确定缩放级别。 - Assaf Lavie
5
@epascarello - 是的,但这并不否定我的评论。 - HorusKol
7
我想确认最新的浏览器是否会触发'onresize'事件。目前我看到最新版本的Chrome(33), FF(28),IE(11和9th模式下的11)在缩放时都能正确地触发该事件。 - Brock
显示剩余5条评论
16个回答

84

无法主动检测是否有缩放。我在这里找到了一个很好的入门指南,讲述了如何尝试实现它。

我发现了两种检测缩放级别的方法。一种检测缩放级别变化的方法是基于百分比值不受缩放影响的事实。百分比值相对于视口宽度,因此不受页面缩放的影响。如果插入两个元素,一个使用百分比位置,另一个使用相同的像素位置,则它们将在页面缩放时移开。找到两个元素位置之间的比率,即可得到缩放级别。请参见测试用例。 http://web.archive.org/web/20080723161031/http://novemberborn.net/javascript/page-zoom-ff3

你也可以使用上面文章中提到的工具来实现缩放检测。但问题是你更多地是在猜测页面是否已经缩放。这在某些浏览器中效果会比其他浏览器好。

如果他们在缩放状态下加载你的页面,就无法判断页面是否被缩放。


1
可以在body的onload处理程序中检查大小参数。有趣的是,在调试IE8与9时,似乎在IE8中,body的onresize处理程序会传递文档,而IE9和Chrome则传递窗口。 - Walter K
如果您将两个元素放置在完全相同的位置,并且页面在缩放时加载,那么这两个元素之间是否会有位移?这不会告诉我们页面是否已经被缩放了吗? - vijayant
也包括document.body.offsetWidth - Samie Bencherif
使用这样的方法也会在用户调整窗口大小时触发,那么为什么不直接使用resize事件监听器呢?在这种情况下,它也是一种可行的解决方案。 - hewiefreeman

36

让我们来定义px_ratio:

px_ratio = 物理像素和CSS像素的比例。

如果有人缩放页面,视口像素(px)会减少并应适应屏幕,因此比例(物理像素/CSS像素)必须变大。

但在窗口调整大小时,屏幕尺寸以及像素(px)也会减少,因此比例将保持不变。

缩放:触发窗口调整大小事件-->并更改px_ratio

但是

调整大小:触发窗口调整大小事件-->不会改变px_ratio

//for zoom detection
px_ratio = window.devicePixelRatio || window.screen.availWidth / document.documentElement.clientWidth;

$(window).resize(function(){isZooming();});

function isZooming(){
    var newPx_ratio = window.devicePixelRatio || window.screen.availWidth / document.documentElement.clientWidth;
    if(newPx_ratio != px_ratio){
        px_ratio = newPx_ratio;
        console.log("zooming");
        return true;
    }else{
        console.log("just resizing");
        return false;
    }
}

关键点在于CSS PX和物理像素之间的区别。

https://gist.github.com/abilogos/66aba96bb0fb27ab3ed4a13245817d1e


7
我认为这应该是被采纳的答案。目前为止运行得非常完美,谢谢!如果有人感兴趣,可以将其包装成自定义事件 https://gist.github.com/souporserious/b44ea5d04c38c2e7ff32cd1912a17cd0。 - souporserious
对我来说,缩放不会触发调整大小事件(Chrome + Firefox) - klm123
@klm123 对我来说,直到今天为止,它在Firefox 92.0(64位)上仍然可以工作。 - Abilogos
2
我的错误,缩放确实会触发调整大小事件。 - klm123
1
超级干净和有效的解决方案。在两个方面都起作用。 - MattGarnett
官方的MDN文档包含更多信息和稍微不同的方法:Window: devicePixelRatio property - jigritsn

22

好消息,一些人!当缩放比例更改时,更新的浏览器将触发窗口调整大小事件。


Chrome和Firefox在Android上缩放时不会触发resize事件。 - lowtechsun
6
如果你不想让缩放触发重新调整大小的事件,这并不是一个好消息。 - Reahreic
20
更好的消息是实际的缩放事件与调整大小事件不同。 - Vincent
7
两者都正确。已编辑。 - Ben
还可以设置缩放吗? - oldboy

13

我正在使用这段 JavaScript 来响应 Zoom "事件"。
它轮询窗口宽度。 (正如 Ian Elliott 在这个页面上所建议的那样:http://novemberborn.net/javascript/page-zoom-ff3[archive]

在 Chrome、Firefox 3.6 和 Opera 中测试过,但不适用于 IE。

敬礼,Magnus

var zoomListeners = [];

(function(){
  // Poll the pixel width of the window; invoke zoom listeners
  // if the width has been changed.
  var lastWidth = 0;
  function pollZoomFireEvent() {
    var widthNow = jQuery(window).width();
    if (lastWidth == widthNow) return;
    lastWidth = widthNow;
    // Length changed, user must have zoomed, invoke listeners.
    for (i = zoomListeners.length - 1; i >= 0; --i) {
      zoomListeners[i]();
    }
  }
  setInterval(pollZoomFireEvent, 100);
})();

8
调整窗口大小时是否存在误报阳性? - gcb
5
如果您实现一个onresize处理程序,就可以筛选掉那些情况。所以我认为基本的解决方案已经在这里了。 - Stijn de Witt
@StijndeWitt 在阅读完这篇文章后,我开始理解所提出的路径,现在我正在努力解决过滤调整大小事件的问题,特别是在移动设备上。有人能帮忙吗? - lowtechsun
也许你可以使用一个间隔值来区分调整大小和缩放?我的意思是,缩放会立即改变窗口宽度超过x像素。 - Sebastien
1
假阳性是一个问题,没错。如果检查宽高比是否保持不变,那么难道不能消除99.99%的所有假阳性吗?让脚本记住上一次的比例,计算新的比例并检查它们是否相同。加上宽度不同,这样应该就有一个良好的基础了。 - Biepbot Von Stirling

6
这对我很有效:
        var deviceXDPI = screen.deviceXDPI;
        setInterval(function(){
            if(screen.deviceXDPI != deviceXDPI){
                deviceXDPI = screen.deviceXDPI;
                ... there was a resize ...
            }
        }, 500);

只有在IE8上需要这个事件。其他浏览器自然会生成一个调整大小的事件。


4

有一个很棒的插件是由yonran开发的,可以进行检测。以下是他之前在StackOverflow上回答的问题链接:answered。它适用于大多数浏览器。应用非常简单:

window.onresize = function onresize() {
  var r = DetectZoom.ratios();
  zoomLevel.innerHTML =
    "Zoom level: " + r.zoom +
    (r.zoom !== r.devicePxPerCssPx
        ? "; device to CSS pixel ratio: " + r.devicePxPerCssPx
        : "");
}

Demo


不支持Android 4.4.2上的Firefox。在移动版Firefox中,在辅助功能选项中勾选“始终启用缩放”按钮。演示不会对更改做出反应。 - lowtechsun

4
尽管这是一个九年前的问题,但问题仍然存在!我一直在项目中检测调整大小(resize),同时排除缩放(zoom),因此我编辑了我的代码使其能够检测并区分调整大小和缩放。它在大多数情况下都可以正常工作,所以如果对于您的项目来说“大多数”已经足够好了,那么这应该会有所帮助!到目前为止,它可以 100%地检测缩放。唯一的问题是,如果用户变得疯狂(即窗口不断调整大小)或窗口延迟,则可能会将其错误地识别为缩放而不是窗口调整大小。
它的工作原理是检测window.outerWidthwindow.outerHeight的改变作为窗口调整大小,同时检测window.innerWidthwindow.innerHeight的独立变化作为缩放。

//init object to store window properties
var windowSize = {
  w: window.outerWidth,
  h: window.outerHeight,
  iw: window.innerWidth,
  ih: window.innerHeight
};

window.addEventListener("resize", function() {
  //if window resizes
  if (window.outerWidth !== windowSize.w || window.outerHeight !== windowSize.h) {
    windowSize.w = window.outerWidth; // update object with current window properties
    windowSize.h = window.outerHeight;
    windowSize.iw = window.innerWidth;
    windowSize.ih = window.innerHeight;
    console.log("you're resizing"); //output
  }
  //if the window doesn't resize but the content inside does by + or - 5%
  else if (window.innerWidth + window.innerWidth * .05 < windowSize.iw ||
    window.innerWidth - window.innerWidth * .05 > windowSize.iw) {
    console.log("you're zooming")
    windowSize.iw = window.innerWidth;
  }
}, false);

注意:我的解决方案类似于KajMagnus的,但对我而言这个更有效。

2
我不会投反对票,因为我可以看到你有一个可行的解决方案,并且我理解你的代码是如何工作的,但我不建议使用像 0.05 这样的魔数而不提供一个好的解释。 ;-)例如,我知道在 Chrome 中,特别是 10% 是浏览器将缩放的最小量,在其他浏览器中是否也是如此并不清楚。顺便说一下,始终确保您的代码经过测试,并准备好为其辩护或改进。 - Nolo
有趣的方法 - oldboy

3

调整窗口大小事件 通过在 window 上附加事件,然后读取body或其他元素(例如(.getBoundingClientRect()))的值,在现代浏览器上工作。

在一些早期的浏览器中,可以在任何 HTML 元素上注册 resize 事件处理程序。仍然可以设置 onresize 属性或使用 addEventListener() 在任何元素上设置处理程序。但是,调整大小事件只在 window 对象上 (即由 document.defaultView 返回) 触发。只有在 window 对象上注册的处理程序才会接收到调整大小事件

⚠️ 调整标签页或缩放以触发此片段:

window.addEventListener("resize", getSizes, false)
        
function getSizes(){
  let body = document.body
  body.width = window.innerWidth
  body.height = window.innerHeight
  console.log(body.width +"px x "+ body.height + "px")
}

getSizes()


⬤ 另一种现代的替代方案: ResizeObserver API

根据你的布局,你可以监视特定元素的大小调整。

这在“响应式”布局中非常有效,因为当缩放时,容器框会被重新调整大小。

function watchBoxchange(e){
 info.textContent = e[0].contentBoxSize[0].inlineSize+" x  "+e[0].contentBoxSize[0].blockSize + "px"
}

new ResizeObserver(watchBoxchange).observe(fluid)
#fluid {
  width: 200px;
  height:100px;
  overflow: auto;
  resize: both;
  border: 3px black solid;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: 8vh
}
<div id="fluid">
   <info id="info"></info> 
</div>


请注意不要从用户手势事件中过度加载JavaScript任务。只有在需要重新绘制时才使用requestAnimationFrame


3
根据MDN的说法,“matchMedia”是正确的方法。请参考https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#Monitoring_screen_resolution_or_zoom_level_changes。由于每个实例只能同时监视一个MQ,所以如果您对任何缩放级别更改感兴趣,您需要创建一堆匹配器。但由于浏览器负责发出事件,因此它可能仍然比轮询更高效,并且您可以将回调节流或防抖动,或将其固定到动画帧或其他内容-这是一个实现,看起来非常迅速。如果您已经依赖于_throttle或其他内容,请随意进行交换。运行代码片段并在浏览器中缩放,注意标记中的更新值-我只在Firefox中测试过!如果您遇到任何问题,请告诉我。

const el = document.querySelector('#dppx')

if ('matchMedia' in window) {
  function observeZoom(cb, opts) {
    opts = {
      // first pass for defaults - range and granularity to capture all the zoom levels in desktop firefox
      ceiling: 3,
      floor: 0.3,
      granularity: 0.05,
      ...opts
    }
    const precision = `${opts.granularity}`.split('.')[1].length

    let val = opts.floor
    const vals = []
    while (val <= opts.ceiling) {
      vals.push(val)
      val = parseFloat((val + opts.granularity).toFixed(precision))
    }

    // construct a number of mediamatchers and assign CB to all of them
    const mqls = vals.map(v => matchMedia(`(min-resolution: ${v}dppx)`))

    // poor person's throttle
    const throttle = 3
    let last = performance.now()
    mqls.forEach(mql => mql.addListener(function() {
      console.debug(this, arguments)
      const now = performance.now()
      if (now - last > throttle) {
        cb()
        last = now
      }
    }))
  }

  observeZoom(function() {
    el.innerText = window.devicePixelRatio
  })
} else {
  el.innerText = 'unable to observe zoom level changes, matchMedia is not supported'
}
<div id='dppx'>--</div>


2
我建议改进先前通过跟踪窗口宽度来进行的解决方案。您可以使用现有的JavaScript事件系统,在宽度更改时触发自己的事件,并将事件处理程序绑定到它上面,而不是保留自己的事件监听器数组。
$(window).bind('myZoomEvent', function() { ... });

function pollZoomFireEvent() 
{ 

    if ( ... width changed ... ) {
        $(window).trigger('myZoomEvent');
    }
}

Throttle/debounce可以帮助减少处理程序的调用速率。


1
节流/防抖仅在由其他事件触发轮询时才有用(例如鼠标移动或键盘松开)。 - fuzzyTew

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