全屏弹窗必须用100dvh而非100vh,因其能响应地址栏、键盘等真实视口变化;fallback需按height: 100vh;height: 100dvh顺序书写,并用js补偿旧safari;position: fixed必须配inset: 0,结构应为三层且z-index需避免父级堆叠上下文截断。

全屏弹窗必须用 100dvh,不是 100vh
移动端弹窗高度跳变、上蹿下跳,90% 是因为用了 100vh。iOS Safari 地址栏收放、Android 键盘弹出时,100vh 会跟着视口高度剧烈变化(比如从 812px 突然缩到 600px),导致弹窗被“顶上去”或留白。
正确做法是优先用 100dvh —— 它是动态视口单位,能响应地址栏、键盘等真实可用高度变化。降级 fallback 写法要按顺序:
height: 100vh; height: 100dvh;
老版本 Safari(如 iOS 15.6)不支持 dvh,得靠 JS 补偿:
- 初始化时设
document.documentElement.style.height = window.innerHeight + 'px' - 监听
resize和keyboarddidshow(iOS)/focusin(Android)事件更新高度
position: fixed 必须配 inset: 0,不能只写 top/left/right/bottom: 0
只写 top: 0; left: 0; width: 100%; height: 100dvh 在部分 Android WebView 和微信 X5 内核里会失效——容器尺寸为 0,align-items: center 直接不生效。
真正可靠写法是:
.modal-overlay {
position: fixed;
inset: 0; /* 等价于 top: 0; right: 0; bottom: 0; left: 0 */
width: 100vw;
height: 100dvh;
display: flex;
justify-content: center;
align-items: center;
}
注意:inset 是现代标准,兼容 Chrome 111+、Safari 16.4+、Firefox 112+;旧浏览器 fallback 可保留四值写法,但别混用。
内容居中失效?检查子元素是否脱离了 flex 流
写了 justify-content: center; align-items: center 却没居中,常见原因不是 CSS 写错了,而是内容本身逃出了 flex 布局流:
- 子元素用了
position: absolute(比如关闭按钮),它已脱离 flex,align-items对它无效 - 父容器没设
min-height: 1px,微信 X5 内核初次渲染可能漏掉交叉轴计算,加这一行可强制重算 - 在
上直接写display: flex,iOS Safari 和 X5 会忽略,必须包一层<div class="modal-root"> <p>安全写法:弹窗结构固定为三层 —— <code>.modal-overlay(遮罩+flex容器)、.modal-container(仅负责定位)、.modal-content(实际内容,不设position)。遮罩层和弹窗的
z-index必须独立堆叠上下文遮罩层盖不住弹窗,或者弹窗被其他组件压住,往往不是数值大小问题,而是堆叠上下文被父级截断:
-
.modal-overlay设z-index: 100,.modal-content设z-index: 110,但若它们共同父级有z-index: 90且非auto,那整个子树都会被限制在 90 之下 - 遮罩层用
background-color: rgba(0,0,0,0.5),别用opacity: 0.5,否则子弹窗也会变半透明 - 移动端必须显式设
pointer-events: auto到弹窗本身,某些 fixed 嵌套场景下默认值会被覆盖,导致点击无响应
复杂点在于:iOS Safari 的
position: fixed在地址栏变化时可能短暂丢失定位,此时依赖inset比四边单独声明更稳;而微信 Android 的 X5 内核对transform和flex的组合渲染仍有偶发 bug,建议所有弹窗容器加will-change: transform提前告知 GPU。 -











