别再用display:none手搓弹层了,HTML原生popover连打开关闭都为你安排好

2026-09-09 0 570

我电脑里现在还躺着一个年久失修的项目,里面有一个“悬浮提示”组件,是用几个div组成,通过加一个class控制显隐。点击关闭按钮的时候,要先把class去掉,再写一个setTimeout为了等动画结束再设置display:none。最恶心的是做个浅色的遮罩,还得另外用一大块样式。

前几天我偶然翻MDN,看到popover属性,突发奇想把它拿来重构那个提示层。改动少得我自己都惊讶:不需要管display,不需要记录状态。只管一个布尔属性,浏览器自己就把最苦的活全干了。

今天这篇文章,我不打算写包装N层的高深原理,就实打实从零把popover用明白,该讲的坑也带你踩一遍。

先弄明白popover到底是个什么

popover是HTML的一个全局属性。任何元素加上popover后,它就进入“隐藏”状态,只在调用打开方法时才出现。关键点是,它会在当前页面的最上层渲染,不受overflow和z-index干扰;不需要你把它渲染到body外面。

可以简单把popover理解为“浏览器原生支持的浮层”。跟dialog不同,它不会把背景变成不可交互。如果做一个模态框,还是得用它配套的inert去把后面的区域禁掉。但如果做常规的下拉菜单、气泡卡片、轻量通知,那popover就是天生的正主。

第一个例子:一个可以开关的popover

最简单的开启方式是配合一个按钮的popovertarget属性。如下面的代码,button 里的 target 指向一个popover的id。

<button popovertarget="notice">显示通知</button>

<div id="notice" popover>
  <p>这是浏览器原生弹出来的内容。</p>
</div>

打开这段HTML,什么都不用写JavaScript,点击按钮,那个div就会被浏览器以浮层的形式显示出来。再按一次Esc或者点击浮层外的区域,它又会自动关闭。

对,没听错,点击外部区域自动关闭是默认行为。这以前咱们要做几行代码监听全局click才能实现。

不只是开关:popovertargetaction 能指定动作

popovertarget只是一个目标连接。那另外还有一个属性叫popovertargetaction,它有三个值:toggle(默认)、showhide

  • show:强制打开目标popover
  • hide:强制关闭目标popover
  • toggle:如果当前隐藏就打开,如果显示就关闭

例如咱们可以这样做一个“打开”和“关闭”按钮,分开控制同一个浮层:

<button popovertarget="tip" popovertargetaction="show">打开提示</button>
<button popovertarget="tip" popovertargetaction="hide">关闭提示</button>

<div id="tip" popover>
  这是提示的内容
</div>

这样是不是比你自己拿一个全局变量存状态要省心多了?

两大模式:auto 和 manual

popover可以带两种不同的关闭策略:

  • popover="auto":默认值,也叫轻量关闭模式。特点是点击popover外区域、按Esc、或者在同一个同组内打开另一个popover,它都会自动关闭。
  • popover="manual":手动模式。只能通过调用JS或点击带有popovertargetaction=”hide”的按钮来关闭。点击外部和Esc都不会关闭。

比如做一个操作菜单,肯定需要点击外部自动隐藏,所以用auto。但如果你要做的是一个“设置向导”或者“图片预览小窗”,你可能希望用户不能点一下就把它关了,那就更适合manual。

要留意的是,popover元素如果设了popover="manual",它就不会形成“顶层层叠上下文”里的同组互斥效果。auto类型的popover相互之间有互斥。简单说,如果你在同一时间打开两个auto popover,后打开的那个会自动把先前那个关闭。

案例:做一个不带任何JS的收藏图标浮层

咱们拿一个场景来实操:页面上有一批文章卡片,每一张上面有一个“小眼睛”图标,点击后弹出一个层,里面放着“赞、收藏、分享”三个按钮。

按老办法,你得给每个图标写一个事件,然后用一个绝对定位div显示在对应按钮附近。用popover写其实思路不同:把popover元素和卡片放一起,点击卡片里的哪个按钮,按钮就指向对应的popover对象。

先看片段(只列两篇文章,循环生成也行):

<article>
  <h3>这是一篇文章标题</h3>
  <button popovertarget="actions_1">操作</button>
  <div id="actions_1" popover>
    <button>👍 赞</button>
    <button>⭐ 收藏</button>
    <button>↗ 分享</button>
  </div>
</article>

<article>
  <h3>另一篇文章标题</h3>
  <button popovertarget="actions_2">操作</button>
  <div id="actions_2" popover>
    <button>👍 赞</button>
    <button>⭐ 收藏</button>
    <button>↗ 分享</button>
  </div>
</article>

效果如何?你只要点击哪张卡片的“操作”,对应那个popover就会出现;点击页面其他地方,浮层自动消失。两个浮层不会同时打开,因为popover默认auto,天生互斥。这逻辑没写一行JavaScript。

当然,位置还比较朴素,因为没加CSS去锚定它,整体看起来像个居中浮层。如果想要它挨着按钮出现,你可以用锚定定位或绝对定位给pop-over套一层包裹。但这不是popover本身该背的锅。

我还需要说明一点:popover内部内容不要添加tabindex等,因为聚焦会自动进入内部。当打开时如果里面没有可聚焦元素,则焦点会落到popover本身,方便读屏。

把浮层藏到按钮里面:popover + 表单

popover虽然是挂在HTML属性上,但它也能用JavaScript来控制。有时候一个按钮不仅要触发业务逻辑,还要顺便把浮层关掉,那就直接调用 hidePopover()

看一个收藏按钮的实际写法:点击“已收藏”按钮后,先改文字,再把popover关掉:

<button id="favBtn" popovertarget="favPop" popovertargetaction="toggle">收藏这篇文章</button>

<div id="favPop" popover="manual">
  <button id="confirmFav">确定收藏</button>
</div>

<script>
  const confirmBtn = document.getElementById('confirmFav');
  const favPop = document.getElementById('favPop');
  const favBtn = document.getElementById('favBtn');

  confirmBtn.addEventListener('click', () => {
    favBtn.textContent = '已收藏';
    favPop.hidePopover();
  });
</script>

这里popover用了manual,所以如果不点“确定收藏”或者用代码调用hidePopover,它就不会自己消失。这种适合轻量确认场景。

监听 popover 的打开和关闭

如果你需要知道浮层到底开了没,可以在popover元素上监听beforetoggle事件。事件对象里有一个newState属性,值为"open""closed"

const notice = document.getElementById('notice');

notice.addEventListener('beforetoggle', (event) => {
  if (event.newState === 'open') {
    console.log('弹层即将打开');
  } else {
    console.log('弹层即将关闭');
  }
});

配合这个事件你可以做:当popover打开时向服务器发送统计数据;关闭时重置内部状态。这个事件在动画之前触发,所以很适合做一些初始化。

还有一个更晚的toggle事件,在状态彻底变更后再触发。如果你只想在浮层完全消失后清理资源,那用toggle就行。

坑位提醒:为什么popover没样式也要注意层级

popover有一个非常霸道的样式行为:它一旦打开,浏览器会自动把它放到最顶层(top layer)。就是说页面里其他元素,不管z-index多大,都不可能盖住它。

这跟你用position:fixed或absolute完全不同。因为二者所在的层级不一定能超过普通元素。Top layer可以理解为浏览器专门开的一层,类似于原生全屏视频。

这一点既是优点也有一点麻烦:你不能在popover上面再放一个自己写的遮罩,或者把popover盖在另一个popover上。不过实际业务中很少遇得到。

关于兼容性,可以放心用吗

到了2025年,现代浏览器Chrome、Edge、Firefox、Safari都已经支持popover和它的触发属性了。如果你在一些旧的WebView里使用,最稳妥的降级办法是提前判断:

if (HTMLElement.prototype.showPopover) {
  // 原生popover可用
} else {
  // 调用自定义的老弹层
}

不过这个特征检测也存在风险,某些老浏览器即使没有showPopover,你也无法阻止它显示出来(因为它不支持popover属性时,只是把它当作普通div,并且初始不隐藏)。这样就会使全部内容在静态页面中漏出。所以更保险起见,可以把popover里原有的内容设计成按钮触发的工具提示,而不是那些没有触发就必须不可见的重要信息。

最后:popover不是弹窗杀手,它是普通浮层的救星

很多人说“以后可以直接用popover替代所有弹窗了”,这是一种误解。真正需要模态框的时候,还是应该用dialog配合modal语义,阻断背景交互。popover更适合那些短暂、非阻断、轻量的提醒。

但你在做下拉菜单、气泡卡、收藏夹、列表筛选、工具提示这类UI时,只要用上popover,就能减少很多手动控制逻辑。

我现在已经打算把我那个老项目里所有用div手搓的浮层全部换成原生的popover。虽然免不了写一些CSS去控制位置和动画,但至少那些“点击外面是否关闭、按Esc能否退出、多个弹层会不会互顶”之类的零碎逻辑都交给浏览器处理了。省下的不是发呆时间,是晚上熬掉的那些头发。

别再用display:none手搓弹层了,HTML原生popover连打开关闭都为你安排好
收藏 (0) 打赏

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

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

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

淘吗网 html 别再用display:none手搓弹层了,HTML原生popover连打开关闭都为你安排好 https://www.taomawang.com/web/html/2733.html

常见问题

相关文章

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

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