首页/文章/javascript

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

2023-12-26
350512 分钟
...

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=9832
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

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

一、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浏览器下的效果:

//zxx: 如果你看到这段文字,说明你现在访问是体验糟糕的垃圾盗版网站,你可以访问原文获得很好的体验:https://www.zhangxinxu.com/wordpress/?p=9832(作者张鑫旭)

三、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浏览器,如下图所示:

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

然而……致命的缺陷

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

考虑到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

也可以扫码体验:

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

相应的JS很简单:

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

兼容性

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

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

五、最后总结一下

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

如何观察呢?

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

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

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

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

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

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

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

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2026-02-24
navigator.sendBeacon全指南
在前端开发中,埋点系统是必不可少的一环。我们经常需要在用户 关闭页面 、 刷新 或 跳转路由 时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。 但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 —— 。 一、 痛点与传统方案的挣扎 场景还原 当用户点击关闭按钮时,浏览器会触发生命周期事件( 或 )。如果我们直接使用普通的异步 AJAX ( 或 ) 发送请求,浏览...
学习
javascript2025-11-02
理解浏览器事件系统,从用户点击到事件对象的完整旅程
深入理解浏览器事件系统:从用户点击到事件对象的完整旅程 “当我点击页面按钮时,背后发生了什么?为什么回调函数能收到一个包含丰富信息的event对象?今天,让我们一起揭开浏览器事件系统的神秘面纱。” 一个令人困惑的现象 作为前端开发者,我们每天都在写这样的代码: 这段代码如此熟悉,以至于我们很少停下来思考:​ ​这个 对象到底从哪里来?它为什么能知道点击的精确坐标?为什么能识别是哪个元素被点击了?​ ​ 更神奇的是,当我们手动创建事件时:...
学习
javascript2025-10-01
实现大文件上传全流程详解
在日常开发中,大文件上传是个绕不开的坎——动辄几百 MB 甚至 GB 级的文件,直接上传不仅容易超时,还会让用户体验大打折扣。最近我用 Vue+Express 实现了一套完整的大文件上传方案,支持分片上传、断点续传、秒传和手动中。 一、先看效果:我们要实现什么? 先上核心功能清单,确保大家明确目标,知道我们要解决哪些实际问题: 大文件分片上传 :将文件切成固定大小的小片段分批上传,避免单次请求超时 秒传 :服务器已存在完整文件时,直接返...
学习
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-15
一张 8K 海报差点把首屏拖垮
你给后台管理系统加了一个「企业风采」模块,运营同学一口气上传了 200 张 8K 宣传海报。首屏直接飙到 8.3 s,LCP 红得发紫。 老板一句「能不能像朋友圈那样滑到哪看到哪?」——于是你把懒加载重新翻出来折腾了一轮。 解决方案:三条技术路线,你全踩了一遍 1\. 最偷懒:原生 一行代码就能跑,浏览器帮你搞定。 🔍 关键决策点 2020 年后现代浏览器全覆盖,IE 全军覆没。 必须写死 ,否则 CLS 会抖成 PPT。 适用场景...
学习
javascript2025-09-10
🚀 Web Worker让你的应用丝滑
🌟 引言 在日常的前端开发中,你是否遇到过这样的困扰: 大数据处理时页面卡死 :处理几万条数据时,页面直接卡成PPT,用户点击毫无反应 复杂计算阻塞UI :图片处理、数据分析等计算密集型任务让整个应用假死 文件上传/下载卡顿 :大文件操作时,其他功能完全无法使用 实时数据处理性能差 :WebSocket接收大量数据时,页面渲染严重滞后 今天分享6个Web Worker的核心技巧,让你的应用告别卡顿,用户体验丝滑如德芙! 💡 核心技巧...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录