← 回到手记
代码

别再手写遮罩层了:原生 <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() 时出现。

常见误区

  1. 只用 open 当模态:实际是非模态,外面还能点。
  2. 没有可见关闭按钮:Esc 在触屏/无键盘设备上不可靠;MDN 强调要提供关闭机制。
  3. 对话框里有 required,取消也关不上:取消按钮加 formnovalidate,或 JS 调 dialog.close()。
  4. 给 <dialog> 加 tabindex:MDN 明确不要加;可聚焦的是内部控件。
  5. 以为点遮罩必关:默认 showModal() 下单击背景不一定关闭,别想当然。

小结

新项目优先试原生 <dialog> + showModal():少维护遮罩和 inert/焦点细节。表单用 method="dialog" 拿 returnValue,背景用 ::backdrop,再留一个看得见的关闭按钮,日常确认框就够用。

参考资料

  1. MDN:
  2. MDN: showModal()
  3. MDN: close()
  4. web.dev: Dialog
✳

谢谢你读到这里。