根本原因是100vh基于固定布局视口,而用户实际看到的是动态视觉视口;应使用@supports包裹的min-height:100dvh兜底100vh,并配合flex-shrink:0和safe-area-inset-bottom等处理键盘与安全区问题。

为什么弹窗用 100vh 会错位
弹窗在移动端底部露出白边、键盘弹起后被顶出视口、或者 iOS 滚动时突然“跳一下”,根本原因不是 CSS 写漏了,而是 100vh 计算的是布局视口(含地址栏/工具栏),而用户真正看到的是视觉视口——它随滚动、键盘、手势条实时变化。100vh 的值在页面加载那一刻就冻结了,后续工具栏收起或键盘弹出,它不会自动更新。
必须用 min-height: 100dvh,不能用 height: 100dvh
100dvh 是动态视口单位,代表“此刻用户真正看到的高度”,但它只适合做最小高度约束,而非强制撑高。直接写 height: 100dvh 会导致内容超长时被裁剪(比如弹窗里有长列表或表单),且部分安卓 WebView 对 height + dvh 的解析不稳定。
- ✅ 正确写法:
min-height: 100dvh,配合max-height: 100dvh和overflow-y: auto - ❌ 错误写法:
height: 100dvh或height: 100dvh; min-height: 100vh(后者会被忽略) - ⚠️ 注意:父容器也得有明确高度上下文,否则
min-height: 100dvh在 flex 容器中可能不生效
@supports 必须包裹,降级要写在前面
不加 @supports 检测,iOS 15、微信 X5 内核等旧环境会把 100dvh 当作无效值,导致整个高度声明失效,弹窗塌陷为 0px 高度。
- ✅ 推荐结构:
min-height: 100vh先写(兜底),再用@supports (min-height: 100dvh)覆盖 - ❌ 不要写成层叠:
min-height: 100vh; min-height: 100dvh—— 旧浏览器遇到第一个就停,第二个根本不解析 - ? 示例:
dialog {
min-height: 100vh;
}
@supports (min-height: 100dvh) {
dialog {
min-height: 100dvh;
}
}
键盘弹起时的子项防压缩关键点
即使用了 100dvh,输入框在弹窗底部仍可能被键盘顶走,是因为 flex 布局默认允许子项收缩(flex-shrink: 1)。键盘弹出后可视区域变小,flex 容器会优先压缩内容区,而不是留出输入框空间。
- 给输入框所在容器加
flex-shrink: 0 - 给弹窗主体内容区加
overflow-y: auto,避免整体挤压 - 安全区兼容:底部按钮需加
padding-bottom: env(safe-area-inset-bottom),否则 iPhone 手势条会遮挡 - ⚠️ 微信 Android(X5 内核)不支持
visualViewportAPI,focusin/blur 后需setTimeout(() => { el.scrollIntoView() }, 100)补偿
真实场景里最易被忽略的,是父容器未设 display: flex 或 height: 100% 就直接对子元素用 align-items: center——这时 100dvh 再准也没用,因为 flex 轴向高度根本没计算来源。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











