100vh 在 ios safari 和微信 webview 中不可靠,因其按固定布局视口计算而非动态可视区域;应优先使用 @supports 包裹的 100dvh,降级时用 js 动态设置 --vh 变量并配合 min-height: calc(var(--vh) * 100)。

因为 100vh 在移动端(尤其是 iOS Safari 和微信 WebView)不是按用户当前看到的可视区域算的,而是按“布局视口”这个固定值算的——它把地址栏、底部工具栏的高度也包进去了,但这些 UI 元素又会滚动时隐藏/显示,导致实际可用高度一直在变,而 100vh 的值从页面加载那一刻就锁死了。
为什么 iOS Safari 里 100vh 总是遮住底部内容
iOS Safari 初始加载时,地址栏和底部工具栏都占空间,但 100vh 却按“全屏物理高度”计算(比如 iPhone 14 是 844px),而真实可视高度可能只有约 700px。结果就是内容被工具栏盖住,或者留一大块白底。
-
height: 100vh比min-height: 100vh更危险:前者会直接截断内容,滚动都拉不到底部 - 这个值在 DOM 加载完成时就确定了,后续地址栏收起、键盘弹出,
100vh完全不响应 - 微信 WebView(基于旧版 WKWebView)行为和 iOS Safari 15.x 一致,
100vh不可靠
100dvh 要用 @supports 包整条规则,不能只包值
直接写 min-height: 100vh; min-height: 100dvh; 是错的。老 Safari(iOS 15.x 及更早)不认识 100dvh,会忽略这行,但保留上一行,问题照旧;新 Safari(iOS 16.4+)也可能因解析顺序或构建工具干扰,依然走 100vh 分支。
- 必须把降级声明写在外面:
.page { min-height: 100vh; } -
@supports (min-height: 100dvh) { .page { min-height: 100dvh; } }—— 整个选择器块都要包进去 - 别用 PostCSS 自动转换单位插件,它们常把
100dvh错误降级成100vh
绝对定位元素设了 100dvh 还是没撑开
position: absolute 元素的百分比高度(包括 100dvh)依赖包含块(containing block)高度。如果父容器没设高度,100dvh 实际计算为 0。
- 常见于弹窗、遮罩层、底部固定按钮——它们的父容器(比如
body或某个 wrapper)没设min-height - 别对绝对定位元素用
height: 100dvh,改用top: 0; bottom: 0;,更稳定 - 若父容器本身靠
--vh驱动,JS 初始化必须先于子元素渲染,否则读到的是初始1px
需要兼容 iOS 15 及更早?JS 动态设 --vh 是唯一解
此时 100dvh 完全不识别,页面会塌成一条线。必须用 JS 回退:
- 在
DOMContentLoaded后立即执行一次,否则首屏渲染用的是地址栏未收起时的错误初始值 - 监听
resize(横竖屏)、scroll(iOS 地址栏收起时仅触发 scroll)、以及微信特有的weixinjsbridgeReady - 防抖必须用
requestAnimationFrame,setTimeout在微信安卓下容易卡死 - CSS 中统一用
min-height: calc(var(--vh, 1px) * 100),不依赖父容器是否设了height: 100%
真正容易被忽略的点是:所有方案都默认你用了 min-height 而非 height,且所有 JS 方案首次执行时机必须卡在 DOM 就绪前——晚一帧,就可能闪一下或错一屏。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











