如何使用JS检测用户是否缩放了页面?

这篇文章发布于 2021年02月15日,星期一,22:17,归类于 JS实例。 阅读 30555 次, 今日 25 次 14 条评论

 

页面缩放占位图

介绍我自己知道的几个检测用户是否缩放了页面的方法,这些方法都不算完美,大家根据实际场景自行抉择该使用哪个方法。

一、resize事件 + devicePixelRatio

如果希望知道用户是否执行了缩放行为,则可以在resize事件事件中检测设备像素比的变化,也就是window.devicePixelRatio的返回值。

代码示意:

let lastPixelRatio = window.devicePixelRatio;
window.addEventListener('resize', function () {
    let currentPixelRatio = window.devicePixelRatio;

    if (currentPixelRatio !== lastPixelRatio) {
        console.log('页面缩放变化了');
    }

    lastPixelRatio = currentPixelRatio;
});

当用户缩放页面的时候会触发窗体的resize事件,以及设备的devicePixelRatio也会跟着变化,于是,我们可以在resize事件中检测devicePixelRatio和缩放事件之前的devicePixelRatio值进行对比,如果变化,则可以认为页面发生了缩放。

但是,上面的方法是有明显的不足的,那就是只能知道页面缩放变化了,但是页面究竟缩放了多少并不知道。

例如,页面原来放大到了120%,然后用户还原到了100%,此时,也认为页面缩放变化了,实际上,此时页面1:1显示,是正常状态,需求上往往是不需要做处理的,但是上面的判断无法识别当前是100%缩放。

那有没有什么法子判断页面真实的缩放比例呢?

还挺难的,因为浏览器会记住当前域名下浏览器的缩放比例,并没有固定的基准devicePixelRatio参照值,因此,使用devicePixelRatio是没法计算页面的缩放比例的。

只能一定程度上进行判断

如果不是追求100%的精准,其实可以配合localStorage本地存储一定程度上判断浏览器的缩放比例,完整JS代码如下:

// 初始缩放比例
let originPixelRatio = localStorage.devicePixelRatio;
if (!originPixelRatio) {
    originPixelRatio = window.devicePixelRatio;
    // 整数才保存
    if (Number.isInteger(originPixelRatio)) {
        localStorage.devicePixelRatio = originPixelRatio;
    }
}

let lastPixelRatio = originPixelRatio;
window.addEventListener('resize', function () {
    let currentPixelRatio = window.devicePixelRatio;
    if (currentPixelRatio !== lastPixelRatio) {
        console.log('缩放比例是:' + Math.round(1000 * (currentPixelRatio / originPixelRatio)) / 10 + '%');
    }
    // 记住上一次的设备像素比
    lastPixelRatio = currentPixelRatio;
});

上面的JS代码效果可以狠狠地点击这里进行体验:resize事件加devicePixelRatio检测是否缩放demo

例如我自己电脑的Chrome浏览器下有如下所示的效果(Ctrl+/Ctrl-改变页面的比例试试):

缩放实现效果示意

可以准备识别用户进行了页面缩放,并准确显示了缩放比例。

但是,上面的实现并不严谨。

如果用户首次进入页面的时候,浏览器的视窗本身就是缩放状态,且此时的设备像素比是整数,则相关的判断就会出问题;虽然这种情况发生概率并不大,但是,理论上,就是会有这样的问题。

所以说,这个方法只能在一定程度上进行缩放比例的判断。

二、matchMedia检测devicePixelRatio变化

resize事件进行判断页面是否缩放会伴随一些不必要的消耗,因为当用户改变窗体的尺寸,或者设备发生旋转,或者设备打开了开发者工具等,都会触发resize事件,但是,显然,这些触发resize事件的场景和页面缩放是没有关系的,而且这些场景才是真正的常态发生的,缩放才是小众发生的。

那有没有其他什么办法更高效地检测浏览器是否发生的页面缩放呢?

此时可以试试matchMedia()方法,语法如下:

let mql = window.matchMedia(mediaString);

mql这个对象包含若干属性和方法,例如:

mql.matches
布尔值,表示当前是否匹配指定的媒体查询。
mql.media
字符串,返回编译后的媒体查询字符串。
mql.onchange
媒体查询改变时候的回调。
mql.addListener(fn)
fn参数是事件函数,媒体查询匹配状态变化时候执行。(此API已经不推荐使用)
mql.removeListener(fn)
fn参数是事件函数,移除绑定的事件。(此API已经不推荐使用)

在本例中,我们使用onchange方法,代码如下所示,初始设备像素比的处理逻辑和上面的resize事件处理一致,大家注意力可以从let mqListener…这行语句开始:

let originPixelRatio = localStorage.devicePixelRatio;
if (!originPixelRatio) {
    originPixelRatio = window.devicePixelRatio;
    if (Number.isInteger(originPixelRatio)) {
        localStorage.devicePixelRatio = originPixelRatio;
    }
}
// 设备相似比变化时候的处理函数
let mqListener = function () {
    let currentPixelRatio = window.devicePixelRatio;
    console.log('缩放比例是:' + currentPixelRatio / originPixelRatio);

    // 移除之前的查询检测
    this.removeEventListener('change', mqListener);
    // 使用新的查询检测
    matchMedia(`(resolution: ${currentPixelRatio}dppx)`).addEventListener('change', mqListener);
};
// 添加媒体查询匹配变化事件
matchMedia(`(resolution: ${originPixelRatio}dppx)`).addEventListener('change', mqListener);

为何要不断移除事件?

matchMedia()绑定的change事件,只会在查询状态改变时候触发,例如,原始的设备像素比(单位就是dppx)是1,则浏览器第一次放大和缩小的时候,change事件会执行,因为状态从匹配变成了不匹配,但是,如果继续放大和缩小,则change事件不再触发,因为状态一直是不匹配。

因此,逻辑实现的时候,需要试试更新媒体查询语句,绑定新的change事件,这样,任意的设备像素比变化都可以被检测到。

眼见为实,您可以狠狠地点击这里:matchMedia + dppx查询检测是否缩放demo

例如,在我的Windows 10 Firefox 85浏览器下的效果:

Firefox浏览器下的缩放与设备像素比

三、outerWidth/innerWidth方法

浏览器的缩放比例还可以使用下面的公式:

let zoom = window.outerWidth / window.innerWidth;

此方法的JS代码比较简洁,如下示意:

window.addEventListener('resize', function () {
    console.log('当前页面缩放比例应该是:' + Math.round(1000 * (outerWidth / innerWidth)) / 10 + '%');
});

也就是把浏览器外部尺寸和窗体内部尺寸的比例作为缩放比例。

这个方法靠谱吗?

我特意整了个demo,您可以狠狠地点击这里:outerWidth/innerWidth与页面缩放demo

在我的Chrome浏览器下,缩放效果如下GIF所示:

缩放实现效果示意

再看看Safari浏览器,如下图所示:

Safari浏览器截图

卧槽,好像很牛逼的样子,居然都可以识别。

然而……致命的缺陷

首先是小缺陷,那就是Firefox浏览器此方法无效,页面缩放的时候,innerWidth尺寸似乎没有明显变化,例如下图是页面放大150%时候的效果,但是outerWidth / innerWidth计算值还是接近与1.

比例是1在Firefox下

考虑到Firefox用户在国内的占比,此问题算是小缺陷,真正的致命的缺陷是下面这个:

当开发开发者工具,同时浏览器侧面定位的时候,缩放比例计算会有严重的偏差,因为此时innerWidth的尺寸严重缩水。

比方说下图所示的场景,Chrome浏览器下,页面并未缩放,但是开发者工具在侧边框打开,显示的缩放比例远远大于100%:

侧边框显示缩放比例不对

然后有些国产浏览器会有自己的侧边栏,里面放了些收藏夹或者其他乱七八糟东西,也会影响innerWidth的尺寸。

因此outerWidth/innerWidth方法虽然简单,但是却有使用风险。

活跃在移动端?

如果不需要考虑Firefox浏览器,以及浏览器绝不会出现侧边栏,则outerWidth/innerWidth方法真是一个上上之选。

嘿,貌似移动端项目就符合这一点。

手机屏幕本来宽度就有限,是不可能多出侧边栏的,也不用考虑Firefox浏览器。

于是我就自己扫码测了下,结果是……resize事件没触发?

Android 微信、原生浏览器、Chrome和iOS中的微信浏览器都是如此,只有iOS Safari浏览器双指缩放页面时候可以触发计算。

算了算了,移动端应该没戏,移动端不要多想了,还是老老实实使用下面的visualViewport API吧。

因此,此方法不建议单独使用,可以配合前面的matchMedia()方法进行交叉验证,提高用户缩放行为判断的准确性。

四、visualViewport与手势缩放识别

实际上,页面的缩放比例,浏览器是提供了原生的API的,那就是window.visualViewport.scale

visualViewport对象包括很多属性,例如宽高、偏移大小等,其中,我认为最稀缺的属性就是scale,可以返回当前页面因为双指缩放带来的缩放比例。

移动设备专享

visualViewport.scale看起来给浏览器缩放识别带来了曙光,确实如此,但是,如果大家在PC端进行测试,会发现visualViewport.scale永远发挥的是1,无论页面通过 Ctrl+/Ctrl-组合键如何的缩小放大,都是1

这是bug吗?

不是的,因为返回的虚拟视区的pinch-zoom缩放比例,表示手势缩放。

因此,visualViewport.scale只能在移动端使用,正好上面的几个方法均不适用于移动端。

整了个demo,您可以狠狠地点击这里:visualViewport与手势缩放比例demo

也可以扫码体验:

demo二维码

例如,在我的Android微信中,缩放页面就可以看到实时的比例变化,截屏效果如下:

缩放比例移动端截图

相应的JS很简单:

window.visualViewport.addEventListener('resize', function () {
    result.innerHTML = '手势缩放比例:' + this.scale;
});

兼容性

从实用角度讲,兼容性还是可以的,除了Firefox暂时不能使用外,其他现代浏览器均可以使用VisualViewport这个API,详见下图:

VisualViewport兼容性

由于在国内移动端开发不考虑Firefox,以及visualViewport.scale就是给移动端用的,因此,只要项目可以不支持iOS 12,此API都是可以用起来的。

五、最后总结一下

在桌面端,用户缩放浏览器页面的时候,devicePixelRatio设备像素比的值是会跟着变化的,因此,我们可以通过观察devicePixelRatio的变化判断用户是否缩放了浏览器。

如何观察呢?

可以使用resize事件,或者使用matchMedia()方法返回的change事件进行观察,其中,后者针对性更强。

除此之外,如果不考虑Firefox浏览器以及认为用户不会以侧边栏的方式打开开发者工具,则还可以使用outerWidth/innerWidth方法判断当前页面的缩放比例。

以上这些判断均指适用于桌面端浏览器,移动端页面的缩放判断可以使用visualViewport.scale这个只读属性完成,目前iOS和Android操作系统均支持。

好,以上就是我知道的检测页面缩放的方法。

一个人所积累的知识毕竟有限,如果有其他更好的方法,希望不吝赐教!

如果文中有表述不准确的地方,也欢迎以评论形式进行指正。

感谢阅读,如果您觉得本文内容还挺不错,欢迎分享到朋友圈或者各类咨询群,感谢感谢!

(本篇完)

分享到:


发表评论(目前14 条评论)

  1. yowanehaku说道:

    不准,我说csdn每次打开都会提示我页面被缩放了。看来他们也用了相同的方法。我的笔记本上浏览器本没有缩放,但是打开demo显示缩放150%

  2. F说道:

    前两种方法好像都不兼容mac双倍视网膜显示器。
    outerWidth/innerWidth貌似是兼容Mac显示器的。

  3. ashe说道:

    总结:
    在PC端开局初始状态就是缩放过的并且开着devTool在侧边栏
    则无法正确计算出状态

  4. 85摩托说道:

    这个resizeObserver是不是也可以哦?

  5. 傻x说道:

    nice

  6. cleverlin说道:

    以前QQ空间用一个flash来监测zoom

  7. 李德胜说道:

    请问下,有办法直接操作浏览器缩放功能吗?比如chrome

  8. oott123说道:

    检测页面缩放算不算某种反模式实践?感觉能兼容各种缩放比例是最好的。

    • 某某某说道:

      某些网站的所谓”自适应rem布局”已经开始阻止缩放:无论缩放多大,显示都一样,因为所有元素的尺寸实质为视图宽度百分比。

    • 某某某说道:

      回归IE6吧,只要把字体尺寸固定为px单位,就再也没有缩放一说了。

  9. Arthur说道:

    mac视网膜显示器的貌似缩放比例判断会有写问题吧

  10. 某某某说道:

    实测这个属性对于我的标称 Chrome55 WebView 套壳浏览器无用。
    如果做Web应用的保守的开发估计还要再等一年(不过我这厮连MDN都不要了)

    预测在我的Windows笔记本上visualViewport.scale有管用的希望,因为触摸板缩放和Ctrl+/-的相互独立(像移动端)

    高DPI屏幕,上网必须开缩放,结果各大网站都弹框说我的页面缩放了,它们不支持你缩放(恶心人)

    因此就算未来升级了我也会找个脚本把这个APIHook掉。