100vh在移动端超出屏幕是因浏览器按最大视口而非可见区域计算,导致底部白边和布局跳变;推荐优先使用@supports检测的min-height:100dvh,兼容不足时用css变量+requestanimationframe动态设置--vh。

100vh 在移动端超出屏幕,不是你写错了,是浏览器把地址栏高度也算进去了——它按“最大可能视口”算,而不是你此刻能看到的部分。
为什么 100vh 在 iOS Safari 和 Android Chrome 里会多出一截
当你刚打开页面,Safari 地址栏还挂着,window.innerHeight 是约 762px(以 iPhone 13 为例),但 1vh 却被解析为 812px / 100 = 8.12px。于是 100vh 实际是 812px,比你能看到的区域高了约 50px。结果就是底部白边、滚动条意外出现、fixed 元素错位。
更糟的是:滚动后地址栏收起,window.innerHeight 变大,但 CSS 不重计算 100vh,导致布局“跳变”。这不是 bug,是规范早期对“viewport”的定义模糊造成的。
- Android Chrome 同样存在类似行为,尤其在全屏模式或 PWA 场景下
-
min-height: 100vh在 iOS 中更容易失效,因为它的“撑不满”表现比height更隐蔽 - 使用
height: 100%无效,除非html和body都显式设了height: 100%,而它们默认是auto
优先用 100dvh:现代浏览器的原生解法
100dvh 是 CSS Viewport Units Level 2 规范里的动态单位,它始终等于当前可见视口高度的 100%,地址栏收/展时自动响应,无需 JS 干预。
但直接写 height: 100dvh 有风险:iOS 15 及更早、旧版 Samsung Internet、部分 WebView 会完全忽略该声明,导致元素高度塌陷为 auto。
- 必须搭配
@supports检测,且推荐用min-height而非height,避免内容超长时被截断 - 正确写法示例:
.login-page { min-height: 100vh; } @supports (min-height: 100dvh) { .login-page { min-height: 100dvh; } } - 不要写成
height: 100dvh—— 如果内容本身超过一屏,它会强制裁剪,用户根本滑不到下面 -
dvh已在 iOS 16.4+、Chrome 105+、Firefox 110+ 原生支持;Edge 110+ 也已覆盖
env(safe-area-inset-bottom) 适合固定底部交互区
env(safe-area-inset-bottom) 不是用来修复 100vh 的主方案,而是处理「安全区域」的补充手段——比如底部 tabbar 或固定按钮被 iPhone 拖拽条遮挡的问题。
它返回的是设备底部安全区(如 iPhone X+ 的 Home Indicator)的高度,单位是 px,仅在支持 safe area 的环境(iOS Safari、部分 Android WebView)中有效。
- 典型用法:
padding-bottom: env(safe-area-inset-bottom)或bottom: env(safe-area-inset-bottom) - 和
100vh混用要小心:height: calc(100vh - env(safe-area-inset-bottom))仍受地址栏问题影响,不能根治 - 它不解决“首屏撑不满”,只解决“底部被遮挡”;若同时需要两者,应先用
100dvh确保高度准确,再叠加env()微调内边距
JS 动态 fallback 方案(仅需兼容 iOS 15–)
如果项目必须支持 iOS 15 或更老系统,JS 计算是兜底选择,但要注意三个关键点:首次渲染时机、事件节流、CSS 变量回退。
错误做法是只监听 resize:iOS 连续触发几十次,不节流会导致 layout thrashing。
- 初始化必须在
DOMContentLoaded之前或之时执行,否则首屏闪动或白屏 - 用
requestAnimationFrame节流最稳妥:function setVh() { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } document.addEventListener('DOMContentLoaded', () => { setVh(); window.addEventListener('resize', () => requestAnimationFrame(setVh)); window.addEventListener('orientationchange', () => requestAnimationFrame(setVh)); }); - CSS 中必须带回退:
height: calc(var(--vh, 1vh) * 100),其中1vh是给不支持 CSS 变量的老浏览器的兜底 - 别用
window.matchMedia('(orientation: portrait)').addEventListener替代orientationchange,后者在 iOS 上更可靠
真正麻烦的不是代码怎么写,而是不同机型在地址栏收起瞬间的 innerHeight 抖动——哪怕用了 requestAnimationFrame,也可能差 1–2px。这时候 dvh 的稳定性优势就非常明显了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











