100vh在移动端因按最大视口计算导致高度失真,引发滚动和布局跳变;推荐优先使用height: 100vh; height: 100dvh兜底,或采用min-height+flex+JS同步window.innerHeight的可靠方案。

100vh 在 iOS Safari 和 Android Chrome 中算的是“最大视口”,不是你看到的那块
你设了 height: 100vh,但移动端浏览器(尤其是 Safari)在页面加载时,会按“地址栏收起前的最大高度”来计算 1vh。比如 iPhone 13 的屏幕高是 812px,它就直接拿 812 ÷ 100 = 8.12px 作为 1vh,结果 100vh 就是 812px —— 可你刚打开页面时地址栏还挂着,实际可视高度只有约 762px,多出来的 50px 就硬生生把内容顶出屏幕,触发滚动条。
更麻烦的是:滚动后地址栏收起,window.innerHeight 变大了,但 CSS 不重算 100vh,布局就“跳”一下。这不是 bug,是早期 viewport 规范没定义清楚动态行为。
- Android Chrome 同样会把地址栏高度算进 100vh,只是时机和策略略有不同
- 这种偏差在 Grid 布局里被放大:因为
grid-template-rows: 1fr auto完全依赖父容器高度,父高失真 →1fr分配错 → 内容撑开或压缩 → 滚动条出现 - 别信“清掉 body margin 就行”——那是桌面端常见原因,移动端这问题跟 margin 无关
为什么 overflow: hidden 在 Grid + 100vh 下基本没用
overflow: hidden 只管自己盒子里的内容溢出,但移动端滚动条往往来自 html 或 body 层级的尺寸错位。比如:
-
body设了height: 100vh,子元素又用了min-height: 100vh,等于叠了两层“全屏”,必然超 - Grid 容器用了
box-sizing: content-box(默认),再加个padding: 20px,总高就是100vh + 40px - 没重置
html, body { margin: 0; height: 100%; },哪怕只多 4px,也够触发滚动条
这时候给 Grid 容器加 overflow: hidden,只是把内容裁掉,用户根本看不到底部按钮——这不是修复,是掩盖。
用 100dvh 替代 100vh 是最简方案,但必须写对顺序
100dvh 是动态视口单位,Safari 16.4+、Chrome 105+、Firefox 112+ 都支持,它会随地址栏收放实时更新。但旧版 Safari 会直接忽略这一行,所以必须兜底:
- 务必把
height: 100vh写在前面,height: 100dvh写在后面,否则老浏览器连 fallback 都没有 - 配合
,否则 Safari 不响应地址栏变化 - 示例写法:
section.fullscreen {<br> height: 100vh;<br> height: 100dvh;<br>}
注意:100dvh 在 iOS 15 或某些 iPad Chrome 版本中仍有不稳定表现,不能无脑全量替换。
Grid 场景下更稳妥的降级策略:min-height + flex + JS 同步
如果项目要兼容 iOS 15 及更低版本,或者你发现 dvh 在测试机上仍会跳变,建议放弃纯视口单位:
- Grid 容器改用
min-height: 100vh,避免强制截断;同时确保子元素不设height: 100%这类刚性约束 - 对关键布局(比如 header + main + footer),用
display: flex; flex-direction: column替代 Grid,flex 对视口变化更宽容 - 监听
window.innerHeight并同步更新 CSS 变量:function setViewportHeight() {<br> document.documentElement.style.setProperty('--vh', `${window.innerHeight}px`);<br>}<br>window.addEventListener('resize', setViewportHeight);<br>setViewportHeight();然后在 CSS 里用height: var(--vh)
这个 JS 方案看似重,但它才是真正响应“此刻可见区域”的唯一可靠方式——所有其他方案,都是在和浏览器的 viewport 计算逻辑博弈,而它直接绕过规则。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











