轻关闭对话框

Web 功能: <dialog closedby>

现代模态对话框通常支持“轻触关闭”,允许用户通过点击或点按背景(对话框外部区域)来关闭对话框。closedby 属性提供了一种声明式方法,无需自定义 JavaScript 即可启用此行为。

实现

如需启用轻触关闭功能,请执行以下操作:

  1. 将 closedby="any" 添加到 <dialog> 元素中。
  2. 使用 dialog.showModal() 打开对话框。

属性值

  • any:启用轻触关闭(点击背景)、“关闭请求”(Esc 键)和开发者机制(例如 dialog.close())。
  • closerequest:仅启用“关闭请求”和开发者机制。这是模态对话框的默认设置。
  • none:只有开发者机制可以关闭对话框。

设置背景幕样式

使用 showModal() 将对话框作为模态窗口打开时,浏览器会生成一个 ::backdrop 伪元素。此背景幕会覆盖整个视口,并直接位于对话框后面。

/* Style the backdrop to indicate the dialog is modal */
dialog::backdrop {
  background-color: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px); /* Optional: add blur for modern browsers */
}

示例

<!-- MANDATORY: Use closedby="any" to enable light-dismiss behavior -->
<dialog id="myDialog" closedby="any" aria-labelledby="dialogTitle">
  <form method="dialog">
    <h2 id="dialogTitle">Feedback</h2>
    <p>Click outside this box or press Esc to dismiss.</p>
    <button type="submit">Close</button>
  </form>
</dialog>

<button onclick="document.getElementById('myDialog').showModal()">Open Dialog</button>

约束条件和无障碍功能

  • 强制性:使用 closedby="any" 以声明方式启用轻触关闭。
  • 强制性:始终使用 showModal() 打开模态对话框。这可确保对话框位于顶层、焦点被捕获,并且 Esc 键得到处理。
  • 建议:使用 aria-labelledby 或 aria-label 为对话框提供无障碍名称。
  • 请勿:对非模态对话框(使用 show() 打开)使用 closedby,因为它们没有背景幕,也不会触发轻触关闭。
  • 请勿:将 click 事件用于应在关闭之前发生的关键逻辑;而应监听 close 或 cancel 事件。

后备策略

强制性:对于尚不支持 closedby 的浏览器,您必须使用以下脚本检查点击是否发生在对话框内容边界之外,从而实现轻触关闭的后备方案:

const dialog = document.querySelector('dialog');

// Fallback for browsers without closedby support
if (!('closedBy' in HTMLDialogElement.prototype)) {
  dialog.addEventListener('click', (event) => {
    // 1. When clicking the backdrop, the event target is the dialog element itself.
    // Ignore clicks where the target is a child element inside the dialog.
    if (event.target !== dialog) return;

    // 2. Check if the click coordinates fall within the dialog's content box.
    // This distinguishes between a click on the backdrop vs a click on the dialog's background/padding.
    const rect = dialog.getBoundingClientRect();
    const isDialogContent = (
      rect.top <= event.clientY &&
      event.clientY <= rect.top + rect.height &&
      rect.left <= event.clientX &&
      event.clientX <= rect.left + rect.width
    );

    if (isDialogContent) return;

    // 3. Since the click was outside the content area (on the backdrop), manually close the dialog.
    dialog.close();
  });
}