弹窗优先用 <dialog> 元素。焦点陷阱、Esc 关闭、背景遮罩、置顶层级四项由浏览器负责,手写 div 方案需自行补齐,且补齐过程容易漏项。

三种打开方式
| 写法 | 类型 | Esc 可关闭 | 弹窗外可操作 |
|---|---|---|---|
| open 属性 | 非模态 | 否 | 是 |
| show() | 非模态 | 否 | 是 |
| showModal() | 模态 | 是 | 否 |
showModal() 会把元素提升到 top layer,父级的 overflow、transform、z-index 不再影响遮罩效果,同时提供 ::backdrop 伪元素。
最小结构与关闭返回值
<dialog id="dlg">
<h3>删除确认</h3>
<p>删除后无法恢复。</p>
<form method="dialog">
<button value="cancel">取消</button>
<button value="ok">确认删除</button>
</form>
</dialog>
<button onclick="document.getElementById('dlg').showModal()">删除</button>
表单上的 method="dialog" 让按钮直接关闭弹窗,并把 value 写入 returnValue,不必调用 preventDefault,也不会触发页面跳转。
const dlg = document.getElementById('dlg');
dlg.addEventListener('close', () => {
if (dlg.returnValue === 'ok') submitDelete();
});
cancel 与 close 的顺序
按下 Esc 先触发 cancel,随后触发 close。需要拦住关闭动作时,在 cancel 的处理函数中调用 preventDefault(),表单未保存的编辑场景依赖这一步。
样式重置的位置
dialog {
border: 0;
padding: 1.5rem;
border-radius: 10px;
max-width: min(90vw, 32rem);
}
dialog::backdrop {
background: rgb(0 0 0 / 0.5);
}
浏览器默认样式带边框、居中与内边距,重置写在 dialog 选择器上。半透明遮罩只能写在 ::backdrop,写在 dialog 上不生效。
四个高频错误
- 重复调用
showModal()。元素已打开时会抛InvalidStateError,调用前先判断open属性。 - 漏写
method="dialog"。默认提交按 GET 处理并跳转,表单数据变成查询串。 - 用
z-index解决层级。模态层级来自 top layer,z-index只在弹窗内部堆叠时有意义。 - 直接给
dialog加过渡动画。display需要参与动画过程,须配合@starting-style与transition-behavior: allow-discrete。
版本边界
<dialog> 与 ::backdrop 自 Safari 15.4 起可用,更旧版本需要 dialog-polyfill 或降级为独立页面。closedby 属性用于声明 Esc 与点击遮罩是否关闭,浏览器支持面较新,上线前做属性存在性判断。

