单靠 user-scalable=no 已无效,必须静态写在 head 最前且唯一,完整参数为 width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0;ios 双指缩放由内容溢出触发,需用 touch-action 或 js 拦截多点触控。

单靠 user-scalable=no 已经不能防缩放——iOS 13.4+ 和 Chrome 90+ 会静默忽略它,不是你写错了,是浏览器主动不执行。
viewport 标签必须静态写在 最前面
浏览器只在 HTML 解析初期读取第一个 <meta name="viewport">,后续同名标签、JS 动态插入(比如 document.head.appendChild())、SSR 模板拼接漏控,全无效。常见翻车点:
- 多个模板(如 CMS 或微前端)各自注入一份,实际生效的只是第一个
- 标签被塞进
或紧贴太靠后,解析时已被跳过 - Vue/React 在 mounted 后才加 meta,此时视口策略早已锁定
正确位置:紧贴 <title></title> 后,且仅出现一次。检查方式是看「网页源代码」(右键 → 查看页面源代码),不是开发者工具里渲染后的 DOM。
真正起效的参数组合是 width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0
user-scalable=no 只是辅助提示,起决定作用的是后三者必须同时存在且相等。缺一不可:
- 只写
initial-scale=1.0不配width=device-width,Android 可能 fallback 到 980px 视口再缩放,文字糊、按钮小 - 只写
maximum-scale=1.0,iOS Safari 会无视;必须minimum-scale=1.0和maximum-scale=1.0并存 -
initial-scale=1.0是锚点,漏掉它,minimum/maximum在部分 WebView 中直接失效
推荐完整写法:<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
iOS Safari 仍能双指缩放?那是因为内容溢出
iOS Safari 会检测 document.documentElement.scrollHeight > window.innerHeight,一旦成立,就强制启用双指缩放——这是可访问性策略,不是 bug。此时加 html, body { height: 100vh; overflow: hidden; } 虽能堵住,但轮播图、列表、弹窗全失效。
更合理做法:
- 对轮播容器加
style="touch-action: pan-x",防误缩放但保横向滑动 - 对垂直滚动区加
touch-action: pan-y - 检查是否有未设
max-width的图片、white-space: nowrap的长文本、或硬编码min-width: 1200px的元素
最后防线:JS 拦截多点触控,{ passive: false } 必须显式传
当 viewport 和 CSS 都被绕过时,唯一实操路径是从手势源头拦截:
- 监听
document.documentElement的touchstart,判断e.touches.length > 1,立刻e.preventDefault() - 双击放大需监听
touchend,缓存时间戳,两次间隔 ≤ 300ms 就阻止 - 关键:事件监听器必须带
{ passive: false },否则现代浏览器默认passive: true,preventDefault()会被静默忽略
注意:input 聚焦后 iOS 自动放大、系统级「更大字体」偏好、辅助工具缩放,这些 viewport 和 JS 都管不了——它们属于操作系统层行为,设计阶段就得用相对单位(rem/em)、避免硬编码小字号来兼容。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










