别再手写遮罩层了:原生 <dialog> 做模态框
用原生 <dialog> 和 showModal() 做确认框:浏览器处理遮罩、inert 和 Esc,表单 method=dialog 直接拿 returnValue。
要解决的问题
确认框、表单弹层……前端天天写。常见做法是全屏 div 当遮罩,自己管 overflow: hidden、Esc、焦点和 aria-modal。能跑,但边角容易漏。
HTML 有专门元素:<dialog>。配合 showModal(),浏览器会处理「盖住页面、挡住外面点击、Esc 关闭」。MDN 标为 Baseline Widely available(约 2022 年 3 月起主流浏览器可用)。
下面按开、关、拿结果、踩坑过一遍。
核心概念
<dialog>:对话框(或类似交互)的语义元素。showModal():模态打开。进顶层,带::backdrop;同文档其余内容 inert。见 showModal()。show():非模态,外面仍可点。close(returnValue?):关闭;可选字符串写入returnValue。见 close()。- 表单
method="dialog":在对话框里提交会关窗,状态保存但不当普通 GET/POST 提交;returnValue设为被点中提交按钮的value(MDN)。
日常开关请用 show() / showModal(),不要依赖 HTML 的 open 属性(带 open 打开的是非模态)。
示例代码
<dialog id="confirm">
<form method="dialog">
<p>确定删除这条记录?</p>
<button value="cancel">取消</button>
<button value="ok" autofocus>确定</button>
</form>
</dialog>
<button type="button" id="open">删除</button>
const dialog = document.getElementById("confirm");
document.getElementById("open").addEventListener("click", () => {
dialog.showModal();
});
dialog.addEventListener("close", () => {
if (dialog.returnValue === "ok") {
console.log("confirmed");
}
});
要点:用 showModal();表单写 method="dialog",按钮带不同 value,关窗后读 returnValue;给最可能先点的控件加 autofocus。
需要手动关:
btn.addEventListener("click", () => dialog.close("manual"));
遮罩样式:
#confirm::backdrop {
background: rgb(0 0 0 / 0.4);
}
::backdrop 只在 showModal() 时出现。
常见误区
- 只用
open当模态:实际是非模态,外面还能点。 - 没有可见关闭按钮:Esc 在触屏/无键盘设备上不可靠;MDN 强调要提供关闭机制。
- 对话框里有
required,取消也关不上:取消按钮加formnovalidate,或 JS 调dialog.close()。 - 给
<dialog>加tabindex:MDN 明确不要加;可聚焦的是内部控件。 - 以为点遮罩必关:默认
showModal()下单击背景不一定关闭,别想当然。
小结
新项目优先试原生 <dialog> + showModal():少维护遮罩和 inert/焦点细节。表单用 method="dialog" 拿 returnValue,背景用 ::backdrop,再留一个看得见的关闭按钮,日常确认框就够用。
参考资料
谢谢你读到这里。