原生dialog真香现场:用HTML自带的模态框救了我的弹窗代码

2026-09-07 0 779

前两天优化公司一个老后台,只要点“修改”按钮,就会弹出一个白底圆角的div。你猜那弹窗的实现用了多少代码?外层div、遮罩层div、标题栏、右上角关闭按钮、里面一堆class,再配合一个定时器去监听点击遮罩是否关闭。最离谱的是,为了做一套可用的动画,还手动在样式表里加了三个keyframes。

我当时突然想到,好像有一个被咱们忽略了好久的原生元素<dialog>,从HTML5就开始有了,但以前总觉得它太简单,没法用在真实的项目里。这次狠下心,我把它所有的用法捋了一遍,结果发现现在根本不需要写那几十行布局,一个dialog标签就把活全干完了。

最简单的用法,可能刷新你的认知

普通文本直接塞进<dialog>里,浏览器默认是隐藏的。如果给它加一个open属性,它就会立刻显示出来,内联在页面流里面,看起来像一块普通区块。这跟你想象的“弹窗”还不太一样。

<dialog open>
    <p>这是一段测试文字</p>
</dialog>

打开页面你会发现它出现在正文里,而且占据文档流的位置,并不是悬浮在最顶上。真正要把它变成浮层,还得靠JS里的showModal()方法。

现在动手来一个正儿八经的模态框。先准备好dialog标签,里面放标题、内容和关闭按钮:

<dialog id="userDialog">
    <h2>编辑用户</h2>
    <p>这里是弹窗里真正的内容</p>
    <button id="cancelBtn" type="button">取消</button>
    <button id="closeBtn" type="button">确定</button>
</dialog>

别急着把它藏起来,dialog默认就是隐藏的,咱们只需通过JS来打开:

const dialog = document.getElementById('userDialog');
document.getElementById('showBtn').addEventListener('click', () => {
    dialog.showModal();
});

只要调用showModal(),浏览器就会把这个dialog提升到顶层,并且给它一个小小的半透明遮罩背景。最棒的是在非正常情况下,你可以点击键盘的Esc关闭它,也可以点击遮罩区域?这里要泼个冷水,原生dialog点击遮罩区域并不会自动关闭,很多人以为可以。所以别指望能靠点击弹窗外边来关闭,除非你自己加事件监听,或者使用一些隐藏的小技巧。

让dialog看起来像一个真正的模态框

虽然原生dialog没有给我们默认的花哨外观,但它留了非常方便的伪元素::backdrop,你可以通过它设置遮罩的样式。比如说加一层压暗的背景,再加一点模糊感:

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

这一段需要写在CSS里。文章顶部说不要样式标签,我这里只展示代码块,你自己可以加到项目的样式文件里。因为咱们这篇文章就专注分析功能,不搞多余外观。

另外dialog本身也可以加宽高、圆角等样式。如果你忘了设置,它默认宽是自适应内容,高度也由内容撑开。如果内部内容很长,它最大高度可能占据整个视口,然后出现内部滚动条。

点击遮罩关闭?这个坑咱们必须绕过去

很多人说“原生功能就是不太好用”,其实他们想实现点遮罩关闭,但官方没有提供这个默认行为。想做到也不难,思路是监听dialog的点击事件,如果点击的目标是dialog自己(不是内部子元素),那就把它关掉。

dialog.addEventListener('click', (event) => {
    if (event.target === dialog) {
        dialog.close();
    }
});

因为dialog自身的渲染区域包含了内容区和padding区域,当你点到遮罩层时,点击目标会落在dialog元素上。而点内部文字、按钮这类子元素,target不会等于dialog,这样就能区分开来。亲测有效。

如果你用了backdrop-filter,这个点击逻辑依然好用。遮罩是dialog的背景,点击边框也算点击到dialog,所以遮罩关闭就是这么简单。

添加动画?先看原生有没有提供

2024年以后,不少浏览器支持了transition-behavior: allow-discrete以及@starting-style,意味着你可以给dialog写平滑的出现过渡。以前的实现都得用JS监听动画结束,现在可以直接写CSS:

dialog {
    transition: opacity 0.3s ease,
                transform 0.3s ease,
                overlay 0.3s ease allow-discrete,
                display 0.3s ease allow-discrete;
    opacity: 0;
    transform: scale(0.9);
}

dialog[open] {
    opacity: 1;
    transform: scale(1);
}

@starting-style {
    dialog[open] {
        opacity: 0;
        transform: scale(0.9);
    }
}

注意overlaydisplay属性被allow-discrete控制,这两个属性通常不能直接参与transition,但配合这个关键声明,就可以在打开关闭时平滑过渡。这玩意儿比咱们以前自己写的弹窗动画要省事得多。

用表单内置方法直接关闭对话框

除了用JS调用close(),dialog还有一个特别出色的配合对象:form method="dialog"。当dialog里的一个form使用method="dialog",你点击提交按钮时,浏览器会直接关闭dialog,并把按钮的value作为一个返回值传给form的submit事件。

<dialog id="formDialog">
    <form method="dialog">
        <input type="text" name="username" value="阿来" />
        <button type="submit" value="submit">保存</button>
        <button type="button" onclick="this.closest('dialog').close()">取消</button>
    </form>
</dialog>

这里有一个隐性的坑:我建议取消按钮用button类型,并调用close(),因为如果取消按钮的类型是submit并且没有指定value,它会以空值关闭,可能会把表单里已有的值重置。你用按钮属性type=”reset”也可以,但是触发reset事件,不如直接close干净。

更关键的是,在dialog内部,form还有其他字段,表单默认的提交行为会因method="dialog"而被浏览器接管,并不会发生页面跳转或刷新。这一点某些版本的老浏览器并不支持,所以做兼容的时候得判断一下。如果你在业务里需要保存数据,可以在submit事件里阻止默认提交,然后用FormData取数据,再调用fetch异步发送。

一个完整的可运行demo直接抄

下面这个段代码,复制到新的html文件里,浏览器打开点按钮就能跑。没有任何样式依赖,重点看交互。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>原生dialog交互小demo</title>
</head>
<body>
    <button id="openBtn">修改昵称</button>

    <dialog id="nickDialog">
        <form method="dialog">
            <p>输入你的新昵称:<input id="nickInput" name="nickname" type="text" value="旧昵称" /></p>
            <button value="ok" type="submit">确认修改</button>
            <button type="button" onclick="document.getElementById('nickDialog').close()">算了</button>
        </form>
    </dialog>

    <p id="result">当前显示:旧昵称</p>

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

        document.getElementById('openBtn').addEventListener('click', () => {
            dialog.showModal();
        });

        dialog.addEventListener('submit', (event) => {
            // 这是表单button触发后的submit事件
            // 此时form默认行为已经被dialog消化了,但我们仍然可以拿到数据
            const formData = new FormData(event.target);
            const nickname = formData.get('nickname');
            result.textContent = '当前显示:' + nickname;
        });
    </script>
</body>
</html>

在弹窗里点击“确认修改”,dialog会关闭,然后submit事件触发,把新名字同步到下边的结果区域。点击“算了”,则是调用close()关闭,不会更新结果。这个流程几乎零成本。

dialog和popover到底选哪个好

因为最近popover也很火,好多人就问:用dialog还是popover?它们俩有什么区别?我给你捋一捋。

  • dialog.showModal()会打开一个模态框,它会让页面其他部分不可交互,并且自带顶层显示以及焦点锁定。适合表单编辑、确认操作、重要提示,这种不希望用户同时操作背景的场景。
  • popover则更适合轻量级的非模态气泡、菜单、浮层。你点击外部会自动关闭,而且不会阻塞背景的交互,更像是一个“泡泡弹层”。

如果你要有层叠遮罩,且要求打开后背景不能滚动,那dialog是天生合适的。popover甚至不会阻止背景点击。在实际业务里,我一般这么定义:需要用户做决定或填信息就选dialog,只是展示快捷操作就选popover。

聊聊焦点管理这个隐形福利

以前自己写模态框最烦的就是焦点处理。打开弹窗后,你得把焦点移到第一个输入框;关掉后,要把焦点还给原来打开弹窗的按钮。否则让使用键盘辅助的用户直接卡死,不知道焦点跑哪儿去了。

dialog机制天生具备这种焦点管理:

  • showModal()之后,焦点会被移入dialog内部,并能按Tab在弹窗内循环锁定。
  • close()或取消关闭后,焦点会自动回到调用showModal()之前它所在的位置。

所以你不需要写什么focus()、previousActiveElement之类的一堆重置代码。这个可比自己造轮子的体验好太多了。

几个小坑,也实打实地摆出来

第一点:用户按Esc键关闭时,会触发cancel事件,然后才关闭dialog。如果你想和esc作对,可以在cancel事件里调用preventDefault()阻止这次关闭。

dialog.addEventListener('cancel', (event) => {
    event.preventDefault();
    console.log('你就是不能按Esc关闭我这个霸道的弹窗');
});

第二点:showModal()时不能有两个同时打开的模态。如果你连续调用两次showModal,或者试图在一个已经打开的dialog上再showModal,浏览器会报InvalidStateError。所以在打开前可以判断一下dialog.open

if (!dialog.open) {
    dialog.showModal();
}

第三点:在iOS或安卓上的支持度,以及老旧微信内置浏览器的兼容性,可能比你想象中好一丢丢。目前所有现代浏览器都已经彻底支持dialog,但如果你是10年前的老WebView,那就没戏了。建议在功能里加一个检测:如果typeof HTMLDialogElement !== 'undefined',就走原生dialog,否则写成原来的div模拟。这也算渐进增强。

第四点:dialog顶部有一个默认的super漂亮的边框吗?没有,它其实几乎匹配display: block; position: absolute; inset-inline-start: 0; inset-inline-end: 0;。在默认的UA样式里,它的宽是fit-content,有marin左右自动居中,并没有太丑,只是很朴素。样式完全由你掌控。

最后想说的话

原生dialog不是新事物,但可能是因为以前大家把注意力都放在“造轮子”的快乐上,忽略了它。经过这几天的实测,我觉得它在内部系统中已经足够可靠:既帮你省去十几个div,又解决了焦点和层叠的问题。

希望这篇文章能给你一个新的切入点。下次遇到弹窗需求,先问问自己:能用原生dialog解决吗?别急着用UI框架里的另一个封装组件。基础HTML能给你的,可能比你想象的多得多。

原生dialog真香现场:用HTML自带的模态框救了我的弹窗代码
收藏 (0) 打赏

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

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

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

淘吗网 html 原生dialog真香现场:用HTML自带的模态框救了我的弹窗代码 https://www.taomawang.com/web/html/2725.html

常见问题

相关文章

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

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