LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

原生<dialog>元素:别再自己手写Modal弹窗了!

freeflydom
2025年8月5日 9:55 本文热度 43

Modal弹窗,可以说是我们前端UI界面里的“标配”了。但这个组件,恰恰是团队里代码质量的“重灾区”。

我见过太多用div手写的弹窗了:z-index满天飞、焦点管理一塌糊涂、背景页面还能滚动、Esc键也关不掉……这些问题,每一个都是体验上的硬伤。

所以,最近我们团队的新项目,我立了一个规矩:只要是做模态对话框,一律优先使用原生的<dialog>元素。

它不仅能解决上面所有问题,而且代码量少得惊人。这篇文章,我就带大家一步步地,用<dialog>来构建一个功能完善、可以直接拿到项目里用的Modal组件。


HTML骨架

我们不再需要一堆div来模拟结构。HTML的骨架非常简单清晰。

<button class="open-button">打开弹窗</button>
<dialog class="my-modal">
  <header class="modal-header">
    <h2>我是弹窗标题</h2>
    <button class="close-button">×</button>
  </header>
  <div class="modal-body">
    <p>这里是弹窗的主体内容。</p>
    <p>你可以试试按 Tab 键,焦点是不会跑到弹窗外面的。也可以按 Esc 键关闭。</p>
  </div>
  <footer class="modal-footer">
    <button class="confirm-button">确认</button>
  </footer>
</dialog>

这个结构里,headerbodyfooter只是为了样式清晰,核心就是那个<dialog>标签。


核心功能 - 用JS唤醒

我们需要一个简单的脚本来控制dialog的开关。我们可以把它封装成一个简单的类,方便复用。

class Modal {
  constructor(dialogEl) {
    if (!dialogEl || dialogEl.tagName !== 'DIALOG') {
      console.error('需要一个 <dialog> 元素');
      return;
    }
    this.dialog = dialogEl;
    this.closeButton = this.dialog.querySelector('.close-button');
    
    // 把事件监听的this绑定到当前实例
    this.handleBackdropClick = this.handleBackdropClick.bind(this);
    
    this.init();
  }
  init() {
    this.closeButton?.addEventListener('click', () => this.close());
    this.dialog.addEventListener('click', this.handleBackdropClick);
  }
  open() {
    this.dialog.showModal();
  }
  close() {
    this.dialog.close();
  }
  // 实现点击遮罩层关闭
  handleBackdropClick(event) {
    // getBoundingClientRect()可以获取元素的大小和位置
    const rect = this.dialog.getBoundingClientRect();
    const isInDialog = (
      rect.top <= event.clientY && event.clientY <= rect.top + rect.height &&
      rect.left <= event.clientX && event.clientX <= rect.left + rect.width
    );
    if (!isInDialog) {
      this.close();
    }
  }
}
// 如何使用
const dialogEl = document.querySelector('.my-modal');
const openBtn = document.querySelector('.open-button');
const modal = new Modal(dialogEl);
openBtn.addEventListener('click', () => modal.open());

代码解析:

  1. dialog.showModal() : 这是关键。调用它,浏览器会自动处理:

    • dialog放到页面的最顶层(top layer),z-index再高也盖不住它。
    • 显示一个默认的遮罩层。
    • 自动管理焦点,并将页面背景“惰性化”。
  2. dialog.close() : 关闭弹窗。

  3. 点击遮罩层关闭:这是原生<dialog>默认不带的功能,但实现起来很简单。我们监听dialog本身的点击事件,判断点击坐标是否在dialog的矩形区域内,如果不在,就说明点的是遮罩层,此时调用close()方法即可。


美化外观

现在弹窗能工作了,但样子还很丑。我们需要给它和它的遮罩层加点样式。

.my-modal {
  width: min(90vw, 500px); /* 宽度最大500px,但不超过视口宽度的90% */
  border: none;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.2);
  padding: 0; /* 我们用内部元素来控制padding */
}
.modal-header, .modal-body, .modal-footer {
  padding: 1rem 1.5rem;
}
.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #eee;
}
.close-button {
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
}
/* 关键:用::backdrop伪元素,来定义遮罩层的样式 */
.my-modal::backdrop {
  background-color: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(3px);
}

::backdrop 这个伪元素所有关于遮罩层的样式,都应该写在这里。


增加动画 - 让体验更丝滑一些

默认的dialog是瞬间出现和消失的,体验有点生硬。我们可以加点动画。

.my-modal {
  /* ...其他样式 */
  transition: opacity 0.3s, transform 0.3s;
}
/* 默认关闭状态,可以把它藏起来 */
.my-modal:not([open]) {
  opacity: 0;
  transform: translateY(30px);
}
.my-modal::backdrop {
  /* ...其他样式 */
  transition: backdrop-filter 0.3s, background-color 0.3s;
}
.my-modal:not([open])::backdrop {
  backdrop-filter: blur(0);
  background-color: rgba(0, 0, 0, 0);
}

不过,你会发现关闭动画不会生效,因为dialog.close()会立刻让元素从DOM中消失。要实现完美的关闭动画,需要一个小技巧:

// 在Modal类里,改造一下close方法
close() {
  this.dialog.classList.add('is-closing');
  this.dialog.addEventListener('animationend', () => {
    this.dialog.classList.remove('is-closing');
    this.dialog.close();
  }, { once: true });
}
@keyframes slide-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(30px); }
}
.my-modal.is-closing {
  animation: slide-out 0.3s ease-out forwards;
}

这个方法稍微复杂一点,但能实现完美的关闭动画。对于大部分简单场景,没有关闭动画也是可以接受的。


最后我们看看兼容性:

到目前位置,主流浏览器几乎都支持原生dialog,但是值得注意的是 Safari, 它的支持度比起它浏览器稍晚,2022年后才开始支持,对于 Safari 浏览器 我更推荐大家使用等价的 polyfill 去解决。(推荐使用 Chrome 官方的 dialog-polyfill)

分享完毕,谢谢大家🙂

转自https://juejin.cn/post/7532302427423817780


该文章在 2025/8/5 9:55:44 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved