最近做一个后台管理界面,天天要写那种鼠标悬停提示、点按钮弹出的下拉菜单。以前第一反应是引个Bootstrap,或者自己写一段JS去控制class和display。后来偶然发现现代浏览器已经内置了Popover API,这东西居然能用纯HTML属性就实现弹出层。折腾了一下午,把原来的代码重写了一遍,真的爽快。这篇文章就跟你聊聊这个API到底怎么用,顺便给几个能直接抄的案例。
Popover API 到底是个啥?
简单说,它就是让任意一个HTML元素变成“弹层”的机制。你只需要在一个元素上加个 popover 属性,这个元素默认隐藏,并且会被浏览器放在所有普通内容的上层。再找一个按钮,加上 popovertarget 属性指向这个元素的id,点击按钮就能切换它的显示和隐藏。整个过程不需要自己写一行JavaScript。
最基础的写法是这样:
<button popovertarget="my-popover">点我</button>
<div id="my-popover" popover>这是弹出的内容</div>
点击“点我”按钮,下面的div就会出现;再点一下按钮,div就隐藏。按键盘的 Esc 或者点击弹层外面的区域,它也会自动关闭。这套行为是浏览器原生支持的,所以连事件绑定都省了。
Case 1:提示气泡(Tooltip)
很多表单旁边会有一个小问号,鼠标移上去需要显示一段说明文字。以前要么用CSS做悬浮,要么用JS定位,现在直接用一个Popover就能搞定。但是有个小限制:Popover默认是点击触发的,不是hover。所以纯HTML做不了悬停触发,需要配合CSS的 :hover 和 :popover-open 才能实现。不过咱们这篇文章只聊HTML,就不写CSS了,先用点击触发演示结构。
<label>用户名</label>
<input type="text">
<button popovertarget="username-tip">?</button>
<div id="username-tip" popover>
用户名只能包含字母和数字,长度在4到12位之间。
</div>
这段代码在浏览器里跑起来,点一下那个问号按钮,提示内容就会弹出来。再点一下问号或者点页面空白处,提示就没了。这种交互对移动端也友好,因为手机上悬停不方便,点击反而自然。
Case 2:下拉菜单
下拉菜单是后台系统里最常见的组件。以前得用一个按钮加一个div,然后写JS监听外面的点击事件来关闭菜单。有了Popover API以后,结构就变得特别干净。当然,菜单里的点击事件还是得用JS处理,但菜单本身的展示和隐藏完全交给浏览器就足够了。
代码长这样:
<button popovertarget="action-menu">操作</button>
<div id="action-menu" popover>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >编辑</a>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >移动</a>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >删除</a>
</div>
这跟之前的提示气泡没什么区别,因为Popover本身就是个通用弹层。菜单弹出后,你可以用 popovertargetaction="hide" 来让菜单里的某个按钮点击后主动关闭。看这个例子:
<div id="action-menu" popover>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" popovertarget="action-menu" popovertargetaction="hide">编辑</a>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" popovertarget="action-menu" popovertargetaction="hide">移动</a>
<a href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" popovertarget="action-menu" popovertargetaction="hide">删除</a>
</div>
这样可以做到点击菜单项后菜单自动隐藏,不用额外写JS去关。
Case 3:模态弹窗的替代品
很多人用Bootstrap的modal做确认框,其实Popover也能干这事。虽然它不像 <dialog> 那样自带阻塞背景交互,但配合弹出层的特性,可以做一个轻量的确认弹窗。
<button popovertarget="confirm-popover">删除这条数据</button>
<div id="confirm-popover" popover>
<h3>确认删除?</h3>
<p>删除以后就找不回来了,想清楚再说。</p>
<button popovertarget="confirm-popover" popovertargetaction="hide">取消</button>
<button id="confirm-btn">删除</button>
</div>
点“删除这条数据”会弹出确认框,点取消会关闭,点删除走你自己的JS逻辑。因为Popover默认带一个顶层显示的特性,所以不用去调z-index,不用担心被页面上其他元素盖住。这一点真的比手写弹窗省心。
多个按钮控制同一个弹层
Popover API还允许页面上多个按钮同时控制一个弹层,意思是你一个弹层可以在不同地方被触发。这在实现“同一份内容多处展示”时特别好用。
<button popovertarget="share-popover">在头部分享</button>
<button popovertarget="share-popover">在底部分享</button>
<div id="share-popover" popover>
分享链接已复制到剪贴板!
</div>
无论点哪个按钮,都会切换同一个弹层的开合状态。如果你希望一个开一个关,那需要分别指定 popovertargetaction="show" 和 popovertargetaction="hide"。
注意事项和踩到的坑
刚开始用的时候我也踩了几个小坑,说一下帮大家避雷。
- 默认隐藏不等于 display:none。Popover元素的隐藏是浏览器内部实现的,你手动加个
display:block并不能让它强制显示,反而会干扰Popover的正常切换。 - 别内部再嵌套另一个Popover。虽然技术上允许,但弹层嵌套后行为很怪,比如外层关闭时内层还开着。我的建议是一个弹层只管一层,别搞套娃。
- Safari浏览器的支持比较晚。大版本要从17开始才支持。做企业内部后台问题不大,要是做面向公众的网站,还是先查一下兼容性。
- 不能只靠纯HTML实现hover触发。目前Popover触发只认点击,想要鼠标悬停出现效果,要么用CSS让popover在
button:hover时打开,要么用JS手动调用showPopover()。不过这不影响基础用法。 - 表单元素在Popover里需要额外处理。如果你在弹层里放了一个输入框,希望弹层打开时光标自动聚焦到输入框,那浏览器并不会自动帮你聚焦,需要手动調用
focus()。这块还是得写几行JS。
写在最后
Popover API看起来不复杂,但确实解决了前端弹层“重复造轮子”的问题。以前写一个下拉菜单要管状态、管事件、管外部点击关闭,现在这些都交给浏览器了。对我这种偶尔写点后台页面的人来说,省下的时间可以去多摸会儿鱼。
如果你也厌倦了为了一个弹层引入整个UI框架,不如试试把这套原生API用在项目里。代码简单,效果也不差,关键是以后维护的时候不用再去翻那一堆 display:none 和 position:absolute 了。
好,这篇文章就到这里。有问题可以在评论区聊,我看到的都会回。

