Skip to content

dialog 对话框

<dialog> 元素用于创建原生对话框(模态或非模态),无需 JavaScript 库即可实现。它提供了 show()showModal() 方法来控制对话框的显示方式,配合 ::backdrop 伪元素可以轻松实现模态遮罩效果。

前置知识

阅读本节前,建议先了解:details 与 summary 折叠

基础概念

什么是 dialog

<dialog> 是 HTML5.2 引入的语义化元素,用于表示对话框、警告框或交互式弹出窗口。它原生支持模态和非模态两种模式,比自定义的 div 弹窗更易于实现和访问。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>dialog 示例</title>
</head>
<body>
  <!-- 定义对话框(默认隐藏) -->
  <dialog id="myDialog">
    <h2>对话框标题</h2>
    <p>这是对话框内容。</p>
    <button onclick="document.getElementById('myDialog').close()">关闭</button>
  </dialog>

  <!-- 触发按钮 -->
  <button onclick="document.getElementById('myDialog').showModal()">打开对话框</button>
</body>
</html>

两种显示模式

模式方法特征
非模态show()不阻塞页面交互,不显示遮罩
模态showModal()阻塞页面交互,显示遮罩,Tab 陷阱

语法与使用

基本语法

html
<!-- 非模态对话框 -->
<dialog id="dialog1">
  <p>内容</p>
  <button onclick="this.closest('dialog').close()">关闭</button>
</dialog>

<!-- 模态对话框 -->
<dialog id="dialog2">
  <form method="dialog">
    <p>内容</p>
    <button value="confirm">确认</button>
    <button value="cancel">取消</button>
  </form>
</dialog>

JavaScript API

javascript
const dialog = document.getElementById('myDialog');

// 打开非模态对话框
dialog.show();

// 打开模态对话框
dialog.showModal();

// 关闭对话框
dialog.close();

// 关闭并返回值
dialog.close('confirmed');

// 监听关闭事件
dialog.addEventListener('close', () => {
  console.log('关闭原因:', dialog.returnValue);
});

// 监听取消事件(按 ESC)
dialog.addEventListener('cancel', (e) => {
  e.preventDefault(); // 阻止默认的 ESC 关闭行为
});

// 检查对话框是否打开
console.log(dialog.open);

::backdrop 伪元素

模态对话框自动创建遮罩层,可以通过 ::backdrop 自定义样式:

css
dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(4px);
}

详细说明

确认对话框

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>确认对话框示例</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 2rem; }

    /* dialog 基础样式 */
    dialog {
      border: none; border-radius: 12px;
      padding: 0; max-width: 420px; width: 90%;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
    }

    /* 非模态 dialog 默认隐藏 */
    dialog:not([open]) { display: none; }

    /* 模态遮罩 */
    dialog::backdrop {
      background: rgba(0, 0, 0, 0.5);
      backdrop-filter: blur(4px);
    }

    /* 对话框内容 */
    .dialog-header {
      padding: 1.5rem; border-bottom: 1px solid #e2e8f0;
    }
    .dialog-header h2 { font-size: 1.1rem; color: #1e293b; }

    .dialog-body {
      padding: 1.5rem; color: #475569; line-height: 1.6;
    }

    .dialog-footer {
      padding: 1rem 1.5rem; border-top: 1px solid #e2e8f0;
      display: flex; justify-content: flex-end; gap: 0.75rem;
    }

    .btn {
      padding: 0.5rem 1.25rem; border-radius: 6px;
      border: none; cursor: pointer; font-size: 0.9rem;
      transition: all 0.2s;
    }
    .btn-cancel { background: #f1f5f9; color: #475569; }
    .btn-cancel:hover { background: #e2e8f0; }
    .btn-danger { background: #ef4444; color: #fff; }
    .btn-danger:hover { background: #dc2626; }
    .btn-primary { background: #3b82f6; color: #fff; }
    .btn-primary:hover { background: #2563eb; }

    .trigger-btn {
      padding: 0.75rem 1.5rem; background: #3b82f6; color: #fff;
      border: none; border-radius: 6px; cursor: pointer; font-size: 1rem;
    }
  </style>
</head>
<body>
  <h1>确认删除</h1>
  <p>以下示例展示了一个确认删除的对话框。</p>
  <button class="trigger-btn" onclick="confirmDialog.showModal()">删除项目</button>
  <p id="result" style="margin-top:1rem;color:#64748b"></p>

  <!-- 确认对话框 -->
  <dialog id="confirmDialog">
    <div class="dialog-header">
      <h2>确认删除</h2>
    </div>
    <div class="dialog-body">
      <p>您确定要删除这个项目吗?此操作无法撤销,所有相关数据将被永久删除。</p>
    </div>
    <div class="dialog-footer">
      <button class="btn btn-cancel" onclick="confirmDialog.close('cancelled')">取消</button>
      <button class="btn btn-danger" onclick="confirmDialog.close('deleted')">确认删除</button>
    </div>
  </dialog>

  <script>
    const confirmDialog = document.getElementById('confirmDialog');
    const result = document.getElementById('result');

    confirmDialog.addEventListener('close', () => {
      if (confirmDialog.returnValue === 'deleted') {
        result.textContent = '项目已删除。';
        result.style.color = '#ef4444';
      } else {
        result.textContent = '已取消操作。';
        result.style.color = '#64748b';
      }
    });
  </script>
</body>
</html>

表单对话框

使用 <form method="dialog"> 可以在对话框中提交表单值:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>表单对话框示例</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 2rem; }

    dialog {
      border: none; border-radius: 12px;
      padding: 0; max-width: 480px; width: 90%;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
    }

    dialog::backdrop {
      background: rgba(0, 0, 0, 0.5);
      backdrop-filter: blur(4px);
    }

    .form-dialog-header {
      padding: 1.5rem; border-bottom: 1px solid #e2e8f0;
    }

    .form-dialog-header h2 { font-size: 1.1rem; color: #1e293b; }

    .form-dialog-body {
      padding: 1.5rem;
    }

    .form-group { margin-bottom: 1rem; }
    .form-group label {
      display: block; font-size: 0.875rem; font-weight: 500;
      color: #334155; margin-bottom: 0.25rem;
    }
    .form-group input, .form-group select {
      width: 100%; padding: 0.6rem 0.75rem;
      border: 1px solid #d1d5db; border-radius: 6px;
      font-size: 0.9rem; outline: none;
    }
    .form-group input:focus, .form-group select:focus {
      border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.1);
    }

    .form-dialog-footer {
      padding: 1rem 1.5rem; border-top: 1px solid #e2e8f0;
      display: flex; justify-content: flex-end; gap: 0.75rem;
    }

    .btn {
      padding: 0.6rem 1.25rem; border-radius: 6px;
      border: none; cursor: pointer; font-size: 0.9rem;
    }
    .btn-cancel { background: #f1f5f9; color: #475569; }
    .btn-primary { background: #3b82f6; color: #fff; }

    .trigger-btn {
      padding: 0.75rem 1.5rem; background: #3b82f6; color: #fff;
      border: none; border-radius: 6px; cursor: pointer;
    }
  </style>
</head>
<body>
  <h1>用户设置</h1>
  <button class="trigger-btn" onclick="settingsDialog.showModal()">编辑资料</button>
  <p id="status" style="margin-top:1rem"></p>

  <!-- 表单对话框 -->
  <dialog id="settingsDialog">
    <!-- form method="dialog" 会使表单提交时关闭对话框 -->
    <form method="dialog">
      <div class="form-dialog-header">
        <h2>编辑个人资料</h2>
      </div>
      <div class="form-dialog-body">
        <div class="form-group">
          <label for="username">用户名</label>
          <input type="text" id="username" name="username" value="张三">
        </div>
        <div class="form-group">
          <label for="email">邮箱</label>
          <input type="email" id="email" name="email" value="zhang@example.com">
        </div>
        <div class="form-group">
          <label for="role">角色</label>
          <select id="role" name="role">
            <option value="developer">开发者</option>
            <option value="designer">设计师</option>
            <option value="manager">管理者</option>
          </select>
        </div>
      </div>
      <div class="form-dialog-footer">
        <button class="btn btn-cancel" type="submit" value="cancel">取消</button>
        <button class="btn btn-primary" type="submit" value="save">保存</button>
      </div>
    </form>
  </dialog>

  <script>
    const settingsDialog = document.getElementById('settingsDialog');
    const status = document.getElementById('status');

    settingsDialog.addEventListener('close', () => {
      if (settingsDialog.returnValue === 'save') {
        status.textContent = '资料已保存。';
      } else {
        status.textContent = '已取消编辑。';
      }
    });
  </script>
</body>
</html>

非模态对话框

html
<dialog id="tooltipDialog" style="position:absolute;top:50px;left:50px;">
  <p>这是一个非模态提示框,不会阻塞页面。</p>
  <button onclick="this.closest('dialog').close()">关闭</button>
</dialog>

<button onclick="document.getElementById('tooltipDialog').show()">显示提示</button>

实战示例

通知弹窗组件

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>通知弹窗示例</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 2rem; }

    dialog {
      border: none; border-radius: 12px;
      padding: 0; width: 360px;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
    }

    dialog::backdrop { background: rgba(0,0,0,0.3); }

    .notification {
      padding: 1.5rem; display: flex; gap: 1rem; align-items: flex-start;
    }

    .notification-icon {
      width: 40px; height: 40px; border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      font-size: 1.25rem; flex-shrink: 0;
    }

    .notification-success .notification-icon { background: #dcfce7; }
    .notification-warning .notification-icon { background: #fef3c7; }
    .notification-error .notification-icon { background: #fee2e2; }

    .notification h3 { font-size: 0.95rem; margin-bottom: 0.25rem; }
    .notification p { font-size: 0.85rem; color: #64748b; line-height: 1.5; }

    .notification-actions { margin-top: 1rem; display: flex; justify-content: flex-end; gap: 0.5rem; }
    .notification-actions button {
      padding: 0.4rem 1rem; border-radius: 4px;
      border: 1px solid #d1d5db; background: #fff;
      cursor: pointer; font-size: 0.85rem;
    }

    .action-buttons { margin-top: 1.5rem; display: flex; gap: 1rem; }
    .action-btn {
      padding: 0.5rem 1rem; border: none; border-radius: 6px;
      cursor: pointer; font-size: 0.9rem;
    }
  </style>
</head>
<body>
  <h1>通知对话框</h1>

  <div class="action-buttons">
    <button class="action-btn" style="background:#dcfce7" onclick="successDialog.showModal()">成功通知</button>
    <button class="action-btn" style="background:#fef3c7" onclick="warningDialog.showModal()">警告通知</button>
    <button class="action-btn" style="background:#fee2e2" onclick="errorDialog.showModal()">错误通知</button>
  </div>

  <dialog id="successDialog">
    <div class="notification notification-success">
      <div class="notification-icon">&#10004;</div>
      <div>
        <h3>操作成功</h3>
        <p>您的文件已成功上传到服务器。</p>
        <div class="notification-actions">
          <button onclick="successDialog.close()">确定</button>
        </div>
      </div>
    </div>
  </dialog>

  <dialog id="warningDialog">
    <div class="notification notification-warning">
      <div class="notification-icon">&#9888;</div>
      <div>
        <h3>存储空间不足</h3>
        <p>您的存储空间已使用 95%,建议清理不需要的文件。</p>
        <div class="notification-actions">
          <button onclick="warningDialog.close()">稍后处理</button>
          <button onclick="warningDialog.close()" style="background:#3b82f6;color:#fff;border-color:#3b82f6">立即清理</button>
        </div>
      </div>
    </div>
  </dialog>

  <dialog id="errorDialog">
    <div class="notification notification-error">
      <div class="notification-icon">&#10008;</div>
      <div>
        <h3>上传失败</h3>
        <p>文件上传失败,请检查网络连接后重试。</p>
        <div class="notification-actions">
          <button onclick="errorDialog.close()">关闭</button>
          <button onclick="errorDialog.close()" style="background:#3b82f6;color:#fff;border-color:#3b82f6">重试</button>
        </div>
      </div>
    </div>
  </dialog>
</body>
</html>

注意事项

ESC 关闭行为

模态对话框默认支持 ESC 键关闭。如果需要阻止:

javascript
dialog.addEventListener('cancel', (e) => {
  if (unsavedChanges) {
    e.preventDefault(); // 阻止 ESC 关闭
    // 显示确认对话框
  }
});

表单验证

模态对话框中的表单可以使用 HTML5 原生验证:

html
<dialog id="formDialog">
  <form method="dialog">
    <input type="text" required minlength="2">
    <button type="submit" value="ok">提交</button>
  </form>
</dialog>

最佳实践

  1. 使用 showModal() 实现模态:大多数场景应使用模态对话框
  2. 提供关闭方式:确保对话框有关闭按钮和 ESC 键支持
  3. 使用 form method="dialog":简化表单对话框的关闭逻辑
  4. 自定义 backdrop:使用 ::backdrop 创建合适的遮罩效果
  5. 焦点管理:模态对话框自动管理焦点,非模态需要手动处理

下一节

继续学习:语义化 vs div 布局

参考链接