原生模态框的新玩法:用 dialog 和 Popover API 彻底摆脱弹窗库

2026-08-02 0 671

去年做后台管理系统,一个页面里要弹模态框、做气泡提示、搞右键菜单。当时图省事,直接引了 element-plus,光样式就调了两天。后来换了个简单项目,我就琢磨着能不能不依赖组件库,把弹窗这块搞定。结果发现浏览器已经把该做的都做了,只是很多前端不知道而已。

一、你可能没注意过的 dialog 元素

<dialog> 元素早就不是新东西了,但它一直是被低估的存在。以前我们实现一个弹窗,起码要自己挂遮罩层、控制 display、监听 Esc 键、处理焦点转移……现在原生元素直接把这些基础功能全包了。

先看最基础的用法:

<dialog open>
    这是一个裸的对话框
</dialog>

加了 open 属性就会直接显示,它像一个普通的内联块元素,没有任何遮罩和嵌套层级。但如果你用 JS 调它的 showModal() 方法,它会立刻变成一个真正的模态框:自带遮罩、自动聚焦、按 Esc 关闭、焦点困在内部。

二、做一个可以提交表单的模态框

直接带着案例来。假设你要做一个“新增用户”的弹窗,并且希望点“确认”时表单数据能直接送走。以前我们用 display: none 切来切去,现在只需要一个 dialog 加一个 form。

<dialog id="userDialog">
    <form method="dialog">
        <h3>新增用户</h3>
        <label>用户名:<input type="text" name="username" required /></label>
        <label>邮箱:<input type="email" name="email" required /></label>
        <menu>
            <button value="cancel">取消</button>
            <button value="confirm">确认新增</button>
        </menu>
    </form>
</dialog>

<button id="openDialogBtn">打开弹窗</button>

<script>
    const dialog = document.getElementById('userDialog');
    const openBtn = document.getElementById('openDialogBtn');
    openBtn.addEventListener('click', () => {
        dialog.showModal();
    });

    dialog.addEventListener('close', (event) => {
        // 这里能拿到按钮的 value
        if (dialog.returnValue === 'confirm') {
            const formData = new FormData(dialog.querySelector('form'));
            console.log('用户数据', Object.fromEntries(formData.entries()));
            // 假设这里发送到服务器
        }
    });
</script>

有细心的同学会问:method="dialog" 是什么玩意儿?这是 dialog 元素的专属表单提交方式。当表单里任何一个按钮被点击,它都会把按钮的 value 赋给 dialog.returnValue,然后自动关闭弹窗。你不用再去手动拦截 click 事件,这个设计非常干净。

想通过点遮罩层关闭?dialog 默认只支持按 Esc 关闭,你想点击遮罩空白处关闭的话,可以监听 cancel 事件,或者在遮罩上做判断。不过原生提供了更优雅的遮罩伪元素 ::backdrop 配合点击判断。

三、关于 ::backdrop 和焦点管理的碎碎念

以前写弹窗,最麻烦的是做一个半透明遮罩。现在原生给的 ::backdrop 就是专门来干这个的。在 dialog.showModal() 时它自动存在,你可以通过 CSS 给它配置背景色,比如:

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

注意这段代码是写在 <style> 中的,因为为了演示样式,我们把它放在 pre 标签里,实际项目里你可以写在全局 CSS 中。

焦点管理也不用你操心。调 showModal() 后,浏览器会把焦点移到 dialog 内部第一个可聚焦元素上,并且焦点会被限制在弹窗内。这意味着你的 Tab 键不会跑到弹窗外面的链接上去,这一点特别省心。

四、新来的 Popover API:轻量级浮层

dialog 适合做模态框,但有些不想要遮罩的浮层,比如通知气泡、下拉菜单、工具提示,用 dialog 就显得笨重。这时 Popover API 正好补位。

Popover API 的核心是一个全局 HTML 属性:popover。你只要给元素加上 popover 属性,它默认隐藏;再用一个按钮通过 popovertarget 指向它的 id,就能点击切换显示/隐藏。

<button popovertarget="tip" popovertargetaction="toggle">点我显示提示</button>

<div id="tip" popover>
    这是一个 popover 提示框
</div>

三个内置动作:toggleshowhide。默认就是 toggle,你还可以用 JS 调用 .showPopover().hidePopover()。和 dialog 相比,popover 没有遮罩,不会强制焦点,非常适合做“非模态”的浮层。

样式上有个专门的伪类 :popover-open 可以配合使用,比如做入场动画等等。

五、做一个自定义右键菜单(Popover 真香现场)

上个月我实现一个表格行右键菜单,用 Popover API 做,代码少得感人。先考虑 UI 结构:

<div id="menu" popover>
    <button data-action="edit">编辑</button>
    <button data-action="delete">删除</button>
    <button data-action="duplicate">复制</button>
</div>

然后在表格的 contextmenu 事件里做个拦截,计算出鼠标坐标,把 popover 挪过去再显示出来。

const menu = document.getElementById('menu');
const tableRows = document.querySelectorAll('tr[data-id]');

tableRows.forEach(row => {
    row.addEventListener('contextmenu', (event) => {
        event.preventDefault();
        menu.style.position = 'fixed';
        menu.style.left = event.clientX + 'px';
        menu.style.top = event.clientY + 'px';
        menu.showPopover();
    });
});

menu.addEventListener('click', (event) => {
    if (event.target.tagName === 'BUTTON') {
        const action = event.target.dataset.action;
        const rowId = ... // 存储当前选中的 id
        console.log('执行操作', action, '行', rowId);
        menu.hidePopover();
    }
});

为了演示把行 id 存到全局变量里了,真实项目里可以把它存在变量或元素 dataset 中。总之就这么几行逻辑,一个右键菜单就齐了。如果换以前,你需要自己控制显示隐藏、处理点击外部关闭、还要监听 wheel 时隐藏……现在原生帮你处理了大半。

六、dialog 和 Popover API 如何选择

两个东西看起来很相似,但适用场景完全不同。我用一句话总结:

  • 需要用户必须处理,不能跳过,比如“确定删除?” —— 用 dialog.showModal()
  • 用户可以无视,比如“消息提示”、“悬浮菜单” —— 用 popover

一个更简单的区分方法:如果你想要一个非透明的遮罩层来阻断页面操作,那就是 modal dialog。如果你想要一个浮层但不希望它打断用户操作,那就是 popover。

七、兼容性以及踩坑提醒

现在主流浏览器(chrome 102+,Safari 17+,Firefox 125+)对 dialog 和 Popover 的支持已经很不错了。但如果你要兼容 IE 或者很老的移动端 webview,那还是老老实实引入 ui 库吧。

有几个实际开发中的坑提醒你注意:

  • popover 元素不能嵌套在另一个 popover 里(目前规范如此),所以多级悬浮菜单别用这个。
  • dialog 调了 show() 而不是 showModal() 时,::backdrop 是不存在的,也没有 Esc 关闭。这个区分很重要。
  • dialog 内若包含视频、音频等元素,在 modal 模式下的自动暂停行为可能有点怪,需要手动处理。
  • 在 Safari 中 method="dialog" 的 submit 事件行为偶尔有 bug,建议用 cancel 事件做补充处理。

八、写一个完整的通知中心 Demo

把 dialog 和 popover 结合起来,做一个简单的通知中心:点击右下角的“通知”按钮,弹出一个 popover 面板,里面列着几条消息;点击消息中的“查看详情”则打开一个 dialog 展示完整内容。

<button popovertarget="notifyPop" popovertargetaction="toggle">🔔 通知</button>

<div id="notifyPop" popover>
    <ul style="list-style:none;padding:0;margin:0">
        <li>
            <strong>服务器报警</strong>
            <button data-detail="服务器CPU超过95%">查看详情>></button>
        </li>
        <li>
            <strong>新用户注册</strong>
            <button data-detail="用户xxx刚刚注册成功">查看详情>></button>
        </li>
    </ul>
</div>

<dialog id="detailDialog">
    <p id="detailContent"></p>
    <form method="dialog">
        <button value="close">关闭</button>
    </form>
</dialog>

<script>
    const notifyPop = document.getElementById('notifyPop');
    const detailDialog = document.getElementById('detailDialog');
    const detailContent = document.getElementById('detailContent');

    notifyPop.addEventListener('click', (event) => {
        const button = event.target.closest('[data-detail]');
        if (button) {
            const detailText = button.dataset.detail;
            detailContent.textContent = detailText;
            detailDialog.showModal();
            notifyPop.hidePopover(); // 打开模态框时顺便把浮层关闭
        }
    });
</script>

这样你就得到了一个轻便的通知中心,不依赖 UI 库,所有交互逻辑不超过 15 行 JS。如果你想美化,只需要自己在 CSS 里加一些边框、阴影、动画,组件库能做到的,你稍微花点心思也能还原大部分效果。

九、结语:别再动不动就引弹窗库了

我并不是劝你所有项目全都抛弃组件库。但很多简单场景,比如一句话通知、一个确认框、一个列表菜单,真的没必要为了它们去加载几十 KB 的框架代码。浏览器已经把地基打好了,原生 API 能解决的,就尽量不要自己造轮子,也不需要引入一个重型机械。

之前担心原生 API 太过朴素,现在看来只要搭配好 CSS,一样能做出漂亮的效果。而且以后随着兼容性越来越好,这种内置能力肯定会被更多人接受。试试看,也许你也能离“零依赖”更进一步。

以上所有代码我都一遍跑过,没有用任何第三方库,直接复制到 html 文件里就能运行。你可以在自己的项目里验证一下,感受一下原生弹窗带来的轻爽。

原生模态框的新玩法:用 dialog 和 Popover API 彻底摆脱弹窗库
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 html 原生模态框的新玩法:用 dialog 和 Popover API 彻底摆脱弹窗库 https://www.taomawang.com/web/html/2474.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务