100vh在移动端“跳变”是因为其基于动态视口高度计算,地址栏收起或键盘弹出时视口高度实时变化;而100dvh能响应式更新,min-h-[100dvh]更稳妥,避免WebView兼容问题导致白屏。

为什么100vh在移动端会“跳变”
iOS Safari 地址栏收起/展开、Android 键盘弹出时,100vh 会突然从 812px 缩到 600px 左右,导致弹窗底部被截断或上移。这不是 bug,而是 vh 基于“当前视口高度”的定义所致。老方案用 height: 100vh + position: fixed 看似全屏,实则不可靠。
真正能响应动态变化的是 100dvh(dynamic viewport height),它随地址栏、键盘显隐实时更新。Chrome 120+、Safari 17.4+、Firefox 115+ 已原生支持。
-
min-h-[100dvh]比h-[100dvh]更稳妥:当某些 WebView 对dvh支持不完整时,min-h会退回到内容撑高逻辑,避免白屏或高度为 0 - 别用
height: 100dvh直接设死——部分安卓 WebView 渲染异常,min-h是兜底关键 - 旧版 Tailwind(tailwind.config.js 中扩展:
theme: { extend: { spacing: { '100dvh': '100dvh' } } },再写h-[100dvh]
fixed 定位 + inset-0 是全屏覆盖的必要前提
只写 min-h-[100dvh] 不等于全屏弹窗。弹窗必须脱离文档流、覆盖整个视口,否则会被父容器 overflow: hidden 截断,或随页面滚动偏移。
- 必须同时设置:
position: fixed+inset-0(等价于top-0 right-0 bottom-0 left-0) - 漏掉
z-index是高频翻车点:z-50常不够,建议用z-50起步,根据项目实际层级上调(如z-[1000]) - 如果弹窗内还要嵌套滚动区域(比如长表单),父容器必须是
flex flex-col,否则子元素的flex-1 overflow-y-auto无法获得可用高度
Flex 居中比 transform 更健壮
用 top: 50%; left: 50%; transform: translate(-50%, -50%) 在内容超长时容易溢出视口顶部或左侧,且对焦点管理、可访问性无帮助。而 Flex 方案天然适配内容尺寸变化。
立即学习“前端免费学习笔记(深入)”;
- 弹窗容器(overlay)要设:
flex justify-center items-center,不是子元素设 - 内容区(content)必须加约束:
max-w-md w-full max-h-[80vh] overflow-y-auto,否则小屏下直接撑满屏幕、文字贴边 - 微信 X5 内核可能初次渲染忽略
align-items,加min-h-px到 overlay 可强制重算交叉轴 - 避免和
margin: auto或transform混用——Safari 中会叠加偏移,尤其在键盘弹出后
backdrop 层和焦点管理不能省略
视觉上的“全屏”只是第一步。点击遮罩关闭、ESC 响应、Tab 键聚焦循环,这些才是用户真正感知到的“模态”体验。
- 遮罩层必须带半透明背景:
bg-black/50或bg-gray-900/70,否则点击事件无法穿透到 backdrop 上 - 内容区要加
tabindex="-1",并在open时调用.focus(),否则键盘用户打不开焦点流 - 关闭按钮必须可 Tab 到,且
Esc触发后,焦点应回到触发弹窗的按钮上——这和 CSS 无关,但常被当作“CSS 居中做完就结束了”
max-h-[80vh] 在小屏上仍可能让内容区被裁切,因为 vh 单位本身不稳定;而 max-h-[80dvh] 尚未被主流框架广泛支持,目前只能靠 JS 补偿或改用 max-h-[min(80vh,80dvh)] 这类组合写法——但后者需 PostCSS 插件支持,生产环境慎用。


















