弹窗是前端最常写的组件之一,但遮罩层级、焦点管理、滚动锁定、Esc 关闭......每个坑都要自己填。其实浏览器早就内置了一个「官方弹窗」------
<dialog>,兼容性早已达标,很多人却还在手写。
问题场景
产品经理的需求永远简单:「弹个窗,带遮罩,点外面能关,按 Esc 能关,别让背景滚动。」
于是你打开 Element Plus / Ant Design,引入 Modal 组件,包一层,完事。但当你需要零依赖 、或者做浏览器扩展 / 微前端子应用时,组件库的弹窗就成了累赘------样式隔离、层级冲突、焦点逃逸,问题一个接一个。
手写弹窗?position: fixed + z-index: 9999 + 手动监听 keydown + 手动锁 body 滚动 + 手动管焦点......写一次想骂一次。
原因分析
手写弹窗的痛点本质上有四个,全是「浏览器没给你现成能力」导致的:
- 层级不可控 :
z-index是全局状态,和第三方组件、iframe、弹窗套弹窗叠加时必然打架 - 焦点逃逸:Tab 键会把焦点移出弹窗,键盘用户直接「失踪」
- 滚动穿透 :弹窗打开后背景还能滚,得手动
overflow: hidden且要考虑移动端 - 可访问性缺失 :ARIA 属性、
aria-modal、焦点初始化和还原,全得自己写
而 <dialog> 元素从 2022 年起全浏览器支持(Chrome 37+/Firefox 98+/Safari 15.4+),上面四个问题它原生解决。
解决方案
1. 基础用法:showModal() 才是完全体
html
<dialog id="myDialog">
<h2>确认删除?</h2>
<p>此操作不可恢复。</p>
<form method="dialog">
<button value="cancel">取消</button>
<button value="confirm">删除</button>
</form>
</dialog>
js
const dialog = document.getElementById('myDialog');
// 打开(模态:带遮罩、锁背景滚动、焦点自动进入)
dialog.showModal();
// 关闭
dialog.close('confirm'); // 可传返回值
// 监听关闭
dialog.addEventListener('close', () => {
console.log('关闭原因:', dialog.returnValue);
});
关键点 :用 show() 打开的是非模态(无遮罩、不锁滚动),只有 showModal() 才有完整模态行为。form method="dialog" 是官方「免 JS 关闭」方案------点按钮自动关闭并把 value 写进 returnValue。
2. 遮罩样式与动画
css
dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
/* 打开动画(Chrome 117+ 支持 transition) */
dialog[open] {
animation: slide-in 0.3s ease;
}
@keyframes slide-in {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: none; }
}
::backdrop 是弹窗专属的伪元素,层级天然在弹窗之下、页面之上,不需要任何 z-index 博弈。
3. 点遮罩关闭(原生不支持,一行代码补)
js
dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.close(); // 点击的是遮罩区域
});
利用事件冒泡:只有点到 dialog 元素本身(即遮罩/空白区)才关闭,点到内部内容不会触发。
4. 完全可控的焦点管理
showModal() 自动完成:焦点移入弹窗 → Tab 循环在弹窗内 → 关闭后焦点还原到触发元素。零代码,键盘用户和读屏用户直接受益。
要点总结
- ✅
showModal()提供遮罩 + 滚动锁定 + 焦点圈定,四件套免费送 - ✅
::backdrop彻底解决遮罩层级问题,告别 z-index 大战 - ✅
form method="dialog"免 JS 关闭并传值,简单场景不用写监听 - ✅ 点遮罩关闭需自己补 3 行代码,
e.target === dialog判断 - ✅ Esc 键关闭是原生行为(
cancel事件可拦截),移动端需注意手势 - ⚠️ 旧项目要兼容 IE 的别用;动画支持度逐年变好,降级方案是
[open]属性直接显示
下次产品再要弹窗,先想想:真的需要组件库吗?