100vh在移动端“多算”工具栏高度是因为它基于布局视口(含地址栏等可隐藏区域)计算,而用户实际看到的是视觉视口,二者不一致导致初始留白或滚动后内容截断;100dvh才是语义正确的解法,代表此刻真实可视高度,需用min-height并配合@supports降级确保兼容性。

直接用 100dvh 替代 100vh,但必须前置 min-height: 100vh 并包裹 @supports 检测——否则 iOS 15、微信 WebView 等环境会完全塌陷。
为什么 100vh 在盒模型里“多算”了工具栏高度
100vh 基于「布局视口(layout viewport)」计算,它把地址栏、底部导航栏这些可隐藏区域全包进去了。而用户滚动时看到的是「视觉视口(visual viewport)」,两者在移动端不一致。
- 页面刚加载:地址栏展开 →
100vh偏高 → 底部留白 - 用户一滚动:地址栏收起 → 实际可视区变大,但
100vh的像素值冻结不动 → 内容被截、定位错位、flex居中失效 - 盒模型中所有依赖
height或min-height的计算(比如calc(100vh - 60px))都会继承这个偏差
100dvh 是唯一语义正确的现代解法
100dvh(dynamic viewport height)代表“此刻用户真正看到的高度”,浏览器原生响应工具栏显隐,iOS 16.4+、Chrome 109+、Firefox 117+ 已稳定支持。
- 必须用
min-height,不是height:避免内容超长时被裁剪 - 声明顺序不能错:
min-height: 100vh必须写在@supports外层,否则老设备直接忽略整条规则 - 别写成
height: 100vh; height: 100dvh:虽然 Chrome/Safari 会取后者,但 Safari 100dvh) -
@media中不能用100dvh:比如@media (min-height: 100dvh)是非法语法
.page {
min-height: 100vh;
}
@supports (min-height: 100dvh) {
.page {
min-height: 100dvh;
}
}哪些场景不能只靠 100dvh?JS 动态方案仍是必要兜底
微信内置浏览器、QQ 浏览器、iOS 15.x 及更早版本的 WKWebView 根本不认 dvh,CSS 方案在此类环境完全失效。
立即学习“前端免费学习笔记(深入)”;
- 监听
resize(不是scroll),首次执行必须在DOMContentLoaded前或同步运行,否则首屏白屏阶段高度就错 - 更新逻辑必须用
requestAnimationFrame包裹,否则高频resize触发强制同步布局,卡顿明显 - CSS 中写
height: calc(var(--vh, 1px) * 100),注意 fallback 单位是1px不是1vh,否则降级失效 - 只对加了
data-dynamic-height的容器更新,避免全局重排
svh / lvh / -webkit-fill-available 都不是解药
100svh 是最小可视高度锚点,滚动后地址栏收起,它反而比当前可视区还小,导致大面积留白;100lvh 是最大高度,适合启动页但不适合通用容器;-webkit-fill-available 是 WebKit 表单控件内部填充逻辑,在 height 上 iOS 支持不稳定、Chrome 直接忽略,且完全不响应工具栏变化。
- 别用
@supports (height: 100svh)做降级:Safari 不识别该语法,整条规则静默失效 - 固定底部按钮别写
height: 100svh:正确做法是padding-bottom: env(safe-area-inset-bottom) - 所有新单位都不能用于 JS 计算:
getComputedStyle(el).height返回的是字符串"100dvh",不是像素值
真正容易被忽略的点在于:即使用了 100dvh,如果父容器没触发 layout,子元素也可能卡在旧位置——这不是单位问题,是渲染流水线的事,得结合 transform: translateZ(0) 或 will-change: height 主动触发重排。


















