viewport标签必须置于<head>最顶部且仅一次,否则浏览器按默认980px渲染,导致媒体查询失效、布局错位;必需参数为width=device-width和initial-scale=1.0,禁用user-scalable=no等限制性属性。

必须在 <head> 中写且只写一次 <meta name="viewport">,否则响应式布局大概率静默失效——不是 CSS 没生效,是浏览器压根没按你预期的宽度渲染。
viewport meta 标签必须放在 <head> 顶部且唯一
很多项目(尤其是 Vue/React 单页应用)会在组件挂载时用 JS 动态插入 <meta name="viewport">,这会导致:安卓 WebView 忽略、iOS Safari 静默丢弃、DevTools Elements 面板里能看到两个重复标签但只认第一个。真实表现是:@media (max-width: 768px) 死活不触发,哪怕设备宽度明明是 390px。
- 纯 HTML 项目:确保它出现在所有其他
<meta>之后、<title>之前 - Next.js:删掉
next/head里手写的,改用next.config.js的experimental.viewport配置(v13.4+) - Nuxt:统一配
app.head.meta,别在onMounted里用document.head.appendChild - 检查方法:打开 Chrome DevTools → Elements → 展开
<head>,手动搜索name="viewport",确认只有一处
width=device-width 是响应式生效的前提,不是可选项
iOS Safari 和 Android Chrome 默认以 980px 宽度渲染页面。不加这个,width: 100% 就等于 “100% × 980px”,和你屏幕实际宽度完全无关。所谓“页面缩成一团”,根源就在这儿。
-
width=device-width对应设备逻辑像素宽度(如 iPhone 14 是 390px,Surface Pro 是 1368px),不是物理像素,也不是固定值 375/768/1280 - 绝对不要写
width=375或width=1280—— 这直接废掉媒体查询能力 - PC 端也必须写:Windows 系统开启“显示缩放”(125%/150%)时,缺它会导致布局偏移、文字模糊
initial-scale=1 要配对使用,不能单独存在
只写 initial-scale=1 不加 width=device-width,效果等同于没写。两者是绑定关系:前者定义缩放比例,后者定义缩放基准。
-
initial-scale=1防止页面加载瞬间闪动,也避免双击后“突然变小” - 别加
maximum-scale=1.0, user-scalable=no—— iOS 16+ 会因此整个忽略 viewport 声明;WCAG 无障碍审查也会报错 - 安卓部分 WebView(微信/QQ 内嵌)可能强制覆盖
initial-scale,此时需配合text-size-adjust: none(仅旧版有效)或改用clamp()控制字体
动态设置 viewport 的 JS 方案风险极高,慎用
网上流传的“根据 window.devicePixelRatio 计算 scale 并重设 content”方案,在多数现代浏览器中已失效或引发兼容问题。
- Chrome 80+、Safari 14+ 不再允许 JS 动态修改 viewport 缩放参数,
setAttribute('content', ...)可能被忽略 -
target-densitydpi=device-dpi是 Android 2.3 时代的遗留属性,现代浏览器已废弃 - 真正需要适配 DPR 的场景(如高清边框、图标缩放),应通过
data-dpr属性 + CSS 变量实现,而非改 viewport - 如果真要 JS 干预,只做一次初始化,且必须在
DOMContentLoaded前完成,不能等mounted或useEffect
最常被忽略的点:viewport 错误不会抛 JS 错误,也不会在 Console 显示警告,它只是让整个响应式体系“看起来正常,实则失效”。检查一定要去 Elements 面板翻 DOM,而不是靠视觉判断。


















