移动端 safari 的 100vh 总比屏幕短是因为其基于初始视口高度计算且不随地址栏收起/展开动态更新;需用 js 动态设置 --vh 变量并结合 env(safe-area-inset-bottom) 适配,或降级使用 100dvh(ios 15.4+)。

移动端 Safari 的 100vh 为什么总比屏幕短?
因为 Safari 在地址栏收起/展开时会动态调整视口高度,但 vh 单位不会实时响应——它基于初始布局时的视口高度计算,之后就“冻结”了。滚动后地址栏隐藏,实际可视区域变高,100vh 却还卡在旧值上,导致底部留白或内容被截断。
这不是 bug,是规范行为;但对全屏卡片、登录页、引导页这类强依赖“撑满可视区”的场景,非常致命。
- 只在 iOS Safari(含微信内置浏览器、QQ 浏览器等 WebKit 内核)明显,Android Chrome 表现正常
-
100vh在页面加载瞬间可能正确,但用户一滚动就失准 - 用
height: 100%代替?没用——父元素高度本身也依赖vh或未定义
用 env(safe-area-inset-bottom) + CSS 变量模拟动态 --vh
核心思路:不用 vh,改用 JS 读取 window.innerHeight,写入一个 CSS 变量 --vh,再在样式中用 calc(100 * var(--vh, 1vh)) 替代 100vh。这样变量可重设,CSS 就能“活”起来。
必须配合 env(safe-area-inset-bottom) 是因为 iPhone X 及以后机型有刘海/小黑条,安全区域会影响真正可用高度,光靠 innerHeight 还不够准。
- 监听
resize和orientationchange事件,每次触发都更新--vh - 初始化时就要设一次,否则首屏渲染仍错
- 别用
scroll监听——太频繁,且和视口高度变化无直接关系
document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`);
然后在 CSS 里:
section.full-height {
height: calc(100 * var(--vh, 1vh));
}
兼容 fallback 和极端情况处理
JS 失效(禁用、加载失败、执行过慢)时,--vh 会回退到 1vh,结果就是整个元素缩成一条线。必须给兜底:
- 用
@supports区分支持 CSS 变量的环境,老浏览器走100vh原始逻辑(反正它们也没 Safari 那套问题) - 在
:root中声明--vh: 1vh初始值,避免未设置时计算异常 - iOS 15+ 引入了
dvh(dynamic viewport height),可直接用100dvh,但 Android 和旧 iOS 不支持,不能单独依赖 - 如果页面有横竖屏切换,
innerHeight值会突变,需确保 resize 事件能捕获到(部分 iOS 版本对 orientationchange 触发不敏感,resize 更可靠)
为什么不用纯 CSS 方案(比如 min-height: 100dvh)?
dvh 确实是官方解法,但它只在 Safari 15.4+、Chrome 105+ 支持,iOS 15.4 之前所有版本都不认。而大量真实用户卡在 iOS 14.x 或 15.0–15.3。线上统计显示,这部分占比仍超 12%(2024 年中数据)。
更麻烦的是:有些 WebView(如微信 8.0.3x)虽声称支持 dvh,实测却返回 0 或固定值。所以生产环境不敢裸用。
- 目前最稳的组合仍是 JS 动态设
--vh+calc()计算 - 别省略
env(safe-area-inset-bottom)的适配——哪怕你没用到安全区,它也是 Safari 视口高度计算链的一环,影响innerHeight的稳定性 - 测试时务必真机操作:模拟器里的 Safari 行为和真机不同,尤其涉及地址栏动画时
这事没有银弹。每次 Safari 更新都可能微调视口逻辑,所以那个 JS 设置 --vh 的代码,最好封装成独立模块,留好升级入口。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











