网页上那些提示消息,比如“收藏成功”“已添加到购物车”,总得手动点一下才能关,是不是有点烦?尤其手机端,戳那个小叉子贼费劲。其实HTML自带一个popover属性,搭配点CSS和JS小把戏,就能整出那种显示几秒后自己消失的弹窗,像手机顶部的“新消息”横幅一样丝滑。这篇就聊聊怎么用这个属性,搞一个点击按钮出现、过会儿自动溜走的通知,全程不依赖第三方库。
啥是popover
popover是HTML里一个挺新的属性,直接给<div>贴上就能让元素浮在页面最顶层,像个弹窗。这玩意儿分两种模式:一种是popover="auto",点弹窗外头任意地方就能关掉(叫轻触关闭);另一种是popover="manual",必须靠按钮或JS代码才能关,点外面没用。手动挡虽然控制感强,但想实现“自动消失”反而更顺手,因为不会被外部点击意外干掉。举个例子,手机收到微信通知,你不碰它过几秒自己缩回去,这就是自动消失的效果。
基础三件套
先搭个最简架子。一个按钮,一个弹窗,弹窗里写“书签已添加”。弹窗用popover="manual",按钮用popovertarget属性指向弹窗的ID,再用popovertargetaction="show"告诉浏览器“只负责打开”。
<button popovertarget="myPop" popovertargetaction="show">
➕ 收藏到书签
</button>
<div popover="manual" id="myPop">
✨ 书签加好啦
</div>这时候点按钮,弹窗会蹦出来,但永远不消失——因为没写关闭逻辑。手动挡就是这样,打开是按钮的活儿,关闭得另想办法。
自动消失两步走
想要弹窗自己消失,得搞定两件事:先是视觉上淡出或缩没,再是真正从顶层移除(否则虽然看不见,但还占着位置,可能挡其他按钮的点击)。这里用一套组合拳:CSS控制高度渐变为0,JS监听高度变化完成后执行关闭命令。
CSS过渡隐藏术
给所有带popover属性的元素套上一个动画效果。从@starting-style定义的初始高度(比如一行文字的高度1lh)过渡到height: 0,并且加上延迟,让弹窗先完整显示一小会儿再开始缩。
[popover] {
height: 0;
transition: height 0.3s cubic-bezier(0.6, -0.28, 0.735, 0.045) 0.6s;
@starting-style {
height: 1lh;
}
}这段代码的意思是:弹窗刚出现时,高度是一行文本(1lh),然后等待0.6秒(就是延迟时间),接着花0.3秒把高度缩到0,视觉上就像通知“biu”一下消失了。cubic-bezier那个曲线让收缩动作带点弹性,看起来更灵动。有个坑:@starting-style是相对较新的特性,老浏览器可能不认,但大不了弹窗不自动缩,至少还能手动关,不影响基本使用。
JS监听补一刀
光缩成0还不够,弹窗其实还“活着”,只是看不见。得在高度归零的那一刻,调用hidePopover()把它真正关掉。这里用ResizeObserver盯着弹窗的尺寸变化,一旦高度变成0,就执行关闭并停止监听。
const pop = document.querySelector('[popover]');
const observer = new ResizeObserver((entries) => {
if (entries[0].contentBoxSize[0].blockSize === 0) {
observer.unobserve(pop);
pop.hidePopover();
}
});
document.querySelector('button').onclick = () => {
observer.observe(pop);
};代码逻辑:点击按钮时,ResizeObserver开始观察弹窗。当CSS过渡把高度变成0,blockSize属性就为0,此时立刻调用hidePopover()关闭弹窗,并且unobserve停止观察,免得重复干活。注意contentBoxSize[0].blockSize获取的是内容区域的高度(不包括边框和内边距),所以CSS里的height最好直接作用在内容盒上,别被padding干扰。
整个流程跑起来就是:点按钮 → 弹窗显示 → 等0.6秒 → 高度缩到0 → JS检测到高度为0 → 关闭弹窗。完美自动消失,不需要手动点叉。
备胎方案:setTimeout硬核延迟
如果觉得ResizeObserver太绕,或者要兼容更老的浏览器(比如IE遗产),可以用setTimeout加一个定时器。思路更直接:点按钮后,显示弹窗,同时启动一个计时器,时间到了就给弹窗加上一个隐藏类,然后手动调用hidePopover()。
<style>
.fade-out {
transition: opacity 0.3s;
opacity: 0;
}
</style>
<button id="trigger">收藏</button>
<div popover="manual" id="toast">已收藏</div>
<script>
const toast = document.getElementById('toast');
const btn = document.getElementById('trigger');
let timer = null;
btn.onclick = () => {
toast.showPopover(); // 手动显示弹窗
toast.classList.remove('fade-out');
clearTimeout(timer);
timer = setTimeout(() => {
toast.classList.add('fade-out');
setTimeout(() => toast.hidePopover(), 300);
}, 2000);
};
</script>这个方案里,弹窗会先显示2秒,然后添加透明渐隐的类,再等300毫秒过渡结束,最后隐藏。缺点在于定时器和动画时间得手动对齐,万一CSS过渡时间改了,JS里的300也得跟着改,不然要么关早了动画没播完,要么关晚了留个透明壳子。而前面ResizeObserver的方法,CSS改多久JS都能自动适配,更省心。
无JS时的兜底
有些极端情况,访客浏览器关掉了JavaScript。这时候手动挡弹窗就彻底没法关了——因为关闭依赖JS。解决方案是放一个<noscript>备份,里面用popover="auto"(轻触关闭)覆盖掉原来的手动弹窗。同时写一套样式,让自动弹窗一直可见,不被CSS的height:0给藏起来。
<noscript>
<div popover="auto" id="pop">书签已加</div>
<style>
[popover] {
height: 1lh !important;
transition: none !important;
}
</style>
</noscript>
<div popover="manual" id="pop">书签已加</div>没JS时,<noscript>里的内容会生效,用户点按钮(按钮的popovertarget依然有用)弹出一个轻触关闭的普通弹窗,点外面空白就能关掉。虽然不如自动消失酷,但至少功能不崩。
用哪个方案看个人偏好:追求代码优雅且面向现代浏览器,ResizeObserver+CSS过渡那一套最干净;图省事且不怕微调时间,setTimeout简单粗暴。不管选哪条路,记得测试一下弹窗的显示层级——popover默认会盖住大部分元素,但遇到更高层级的<dialog>或全屏视频,可能被压下面,这时候调调z-index也没用,因为顶层层叠规则比较特殊,需要确保没有其他元素也用了popover且后来居上。
