移动端100vh失效是因它基于静态布局视口计算,不响应地址栏、键盘等动态变化;应严格使用min-height: 100dvh并用@supports包裹整条规则块降级,同时兼顾父容器高度依赖、JS动态--vh兼容旧环境。

移动端 100vh 失效不是你写错了,而是它根本就不是为滚动/键盘/地址栏动态变化设计的单位;用 min-height: 100dvh 并严格配合 @supports 规则块,是当前唯一能同时覆盖 iOS Safari 16.4+、Chrome 109+、微信 X5(部分新版)和 SSR 渲染的解法。
为什么 100vh 在 iOS 和微信里总“差一截”或“塌成一条线”
因为 100vh 按页面加载时的「布局视口」静态计算——比如 iPhone 14 物理高 844px,地址栏占 70px,1vh 就是 8.44px,100vh 锁死为 844px。但用户一滚动,地址栏收起,真实可视高度变成约 774px,100vh 却不更新,结果就是内容被裁、底部留白,或者 fixed 元素错位。
- iOS 15.x 及更早、微信 X5 内核完全忽略
100dvh,裸写等于没写 -
height: 100vh比min-height: 100vh更危险:前者强制裁切,内容超长直接滑不到底 - SSR 渲染中,
min-h-screen(即min-height: 100vh)在服务端无window对象,可能 fallback 为空,首屏直接无高度 - 哪怕写了
flex-1,若父容器没flex flex-col,弹性空间就不存在,主区域照样塌陷
怎么正确用 100dvh 替代 100vh
100dvh(dynamic viewport height)才是语义上“当前可见区域高度”的标准单位,iOS 16.4+、Chrome 109+、Firefox 117+、Edge 110+ 已稳定支持。但它不能裸用,必须满足三个硬条件:
-
min-height: 100vh必须写在外层,作为所有浏览器的基础兜底 -
@supports (min-height: 100dvh)必须包裹整个规则块,不能只包值 —— 否则老 Safari 会跳过整条规则 - 构建工具(如 PostCSS)若自动把
dvh降级为vh,必须关掉对应插件(例如postcss-100vh-fix) - Tailwind 中需启用 arbitrary value 支持(v3.3+ 默认开启),直接写
min-h-[100dvh]即可
position: absolute 或 fixed 元素设了 100dvh 还没反应?
百分比高度(包括 100dvh)依赖包含块(containing block)的高度,不是“强制占满屏幕”。常见于弹窗、遮罩层、底部按钮等场景:
立即学习“前端免费学习笔记(深入)”;
- 父容器(如
body或直接祖先)没设min-height: 100dvh或显式高度 → 子元素的100dvh实际计算为0 - 别对
position: absolute或fixed元素用height: 100dvh,改用top-0 bottom-0(或inset-y-0)更可靠 - 侧边栏用
fixed时,必须加inset-y-0 left-0,仅left-0不够 - 若父容器本身靠 JS 注入的
--vh变量驱动,JS 初始化必须先于子元素渲染,否则读到的是初始1px
兼容 iOS 15 及微信旧版 WebView 的唯一路径是 JS
这些环境根本不识别 100dvh,CSS 方案完全失效。必须用 JS 动态注入 --vh 变量,但两个点极易漏掉:
- 首次执行必须在
DOMContentLoaded后立即同步运行,否则首屏渲染用的是地址栏未收起时的错误初始值 - 监听目标不能只用
resize:iOS 地址栏收起只触发scroll;微信还需监听weixinjsbridgeReady;键盘弹出要监听focusin/focusout - 更新必须用
requestAnimationFrame包裹,否则 iOS 下重排频繁、高度跳变 - CSS 中写
min-height: calc(var(--vh, 1vh) * 100),注意是1vh(单位),不是1px或1—— 否则calc整条声明失效
真正容易被忽略的,是 @supports 必须包裹整个选择器块,而不是只包属性值;还有绝对定位元素对包含块高度的强依赖 —— 这两点一旦漏掉,哪怕用了 100dvh,页面照样塌。


















