最稳妥解法是先设min-height: 100vh兜底,再用@supports(min-height: 100dvh)包裹升级规则;100vh在iOS Safari中多出滚动条是因为按物理屏高计算而非实际可视高度,导致初始视口高度失真。

直接用 min-height: 100vh 配合 @supports (min-height: 100dvh) 降级是最稳妥的解法,不是所有场景都适合换单位,更不能裸写 height: 100dvh。
为什么 100vh 在 iOS Safari 和 Android Chrome 中会多出滚动条
它不是你 CSS 写错了,是浏览器把地址栏高度算进初始视口了:页面刚加载时地址栏挂着,window.innerHeight 可能是 762px,但 1vh 却按设备物理屏高(比如 iPhone 13 是 812px)算成 8.12px,于是 100vh 实际是 812px——比你能看见的区域高约 50px。滚动后地址栏收起,视口变大,但 100vh 不重算,内容就被切掉、底部留白,甚至触发意外滚动条。
典型现象包括:overflow: hidden 失效、position: fixed 元素错位、全屏轮播图顶部被遮挡、height: 100% 无效(除非 html 和 body 都显式设了 height: 100%)。
怎么正确使用 100dvh 替代 100vh
100dvh 是当前最干净的解法,代表“此刻用户真正看到的可视区域高度”,地址栏收/展时自动响应。但它不能裸用。
立即学习“前端免费学习笔记(深入)”;
- 必须前置声明
min-height: 100vh,放在@supports外面,否则老 Safari 直接忽略整条规则 - 用
min-height替代height:避免内容超长时被截断,也更符合语义 -
@supports必须包裹整条规则,不能只包值;错误写法:min-height: 100vh; min-height: 100dvh;—— 新 Safari 仍用100vh - 正确写法:
.fullscreen { min-height: 100vh; } @supports (min-height: 100dvh) { .fullscreen { min-height: 100dvh; } }
微信、iOS 15 或旧 WebView 中怎么兜底
微信内置浏览器(X5 内核)、iOS 15.x、部分安卓 WebView 根本不识别 dvh,CSS 方案在此类场景完全失效。JS 是唯一可靠路径,但监听目标不能是 resize —— iOS 地址栏收起时不触发 resize,只触发 scroll。
- 必须监听
visualViewport?.resize(iOS 最准)或scroll+resize+orientationchange - 首次执行要同步运行,在
DOMContentLoaded前或刚进脚本就调用,否则首屏白屏期高度就错 - 设 CSS 变量:
document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`) - CSS 中统一用:
min-height: calc(var(--vh, 1px) * 100);,不依赖父级height: 100%
Grid 或 absolute 定位中容易踩的坑
在 display: grid 容器里用 100vh,问题会更明显:Grid 的行高分配(比如 grid-template-rows: 1fr auto)依赖父容器高度,而 100vh 在 iOS Safari 或 iPad Chrome 中实际值 ≠ 当前可视高度,导致 1fr 高度失真,最终触发滚动条。
- 给
position: absolute元素写min-height: 100dvh,但它的父容器没设明确高度(比如只是position: relative且无height),那它依然不会占满屏幕——dvh依赖包含块,不是“强制撑满窗口” - 确保父容器(如
body或直接祖先)有min-height: 100dvh或显式高度 - 对绝对定位元素,更稳妥的是用
top: 0; bottom: 0;替代height: 100dvh - 别信
-webkit-fill-available能一劳永逸:它只对height有效,min-height不认;不响应软键盘弹起;iPad Chrome 中甚至会叠加导致高度翻倍
真正麻烦的不是写错单位,而是混合使用 dvh 和 svh,或者在 position: fixed 元素上直接套 100dvh 却没设包含块高度——这些地方一错,fixed 元素就飘走,而且很难定位。


















