模态层必须用 position: fixed 而非 absolute,否则滚动时弹窗偏移;居中推荐 flex 布局配合 top/left/right/bottom: 0;点击遮罩关闭需严格判断 event.target === modalbackdrop;键盘关闭须同时处理 escape 和焦点逃逸;移动端 safari 应用 100dvh 或 js 动态设置 --vh 变量替代 100vh。

模态层必须用 position: fixed 而非 absolute
用 absolute 会导致弹窗随页面滚动偏移,用户滚动后点不到遮罩或按钮。固定定位才能锁死在视口中央,且不依赖父容器的 transform 或 overflow 干扰。
常见错误是给模态容器加了 transform: translate(-50%, -50%) 却忘了配合 top: 50%; left: 50% —— 缺一不可,否则偏移计算失效。更稳妥的做法是:
.modal {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1050;
}
这样避免手动计算偏移,也兼容缩放、字体调整等场景。
点击遮罩关闭时,event.target 必须严格判断为遮罩本身
很多人直接监听遮罩的 click 事件,结果用户点到弹窗内部(比如输入框、按钮)也触发关闭 —— 因为事件冒泡到了遮罩层。
正确做法是检查 event.target === modalBackdrop,而不是用 contains() 或类名判断:
modalBackdrop.addEventListener('click', (e) => {
if (e.target === modalBackdrop) {
closeModal();
}
});
如果用了 flex 布局让弹窗居中,遮罩和弹窗是同级元素,这点尤其关键。顺带一提:别用 pointer-events: none 给弹窗设值,否则内部交互全部失效。
键盘关闭逻辑要同时处理 Escape 和焦点逃逸
只监听 Escape 键不够 —— 屏幕阅读器用户可能用 Tab 切出弹窗,导致焦点丢失,这属于可访问性硬伤。
必须做两件事:
- 监听
document.addEventListener('keydown', ...)捕获全局Escape,注意用stopPropagation()防止干扰编辑器等组件 - 在弹窗打开时用
focusin监听,一旦焦点移到弹窗外(!modal.contains(e.target)),立即把焦点拉回第一个可聚焦元素 - 打开时用
inert属性(或手动设tabindex="-1")禁用背景内容的焦点流转,比单纯aria-hidden="true"更可靠
移动端 Safari 的 height: 100vh 会因地址栏收放错位
Safari 在滚动时会隐藏地址栏,100vh 会按“当前可视高度”重算,导致遮罩突然变短、底部露出白边或内容被截断。
解决方案不是换单位,而是绕过 viewport 高度计算:
- 用
min-height: 100dvh(dvh是动态视口单位,iOS 16.4+ 支持) - 降级方案:用 JS 获取
window.innerHeight并写入自定义 CSS 变量,再在 CSS 中引用min-height: var(--vh, 100vh) - 绝对不要用
height: 100%+html, body { height: 100% },这在 iOS 上同样不稳定
这个细节在真机测试前很难暴露,但上线后用户反馈集中在此。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











