弹窗自动关闭怎么整,手把手用popover属性实现消失通知

3,174字
13–20 分钟
in

网页上那些提示消息,比如“收藏成功”“已添加到购物车”,总得手动点一下才能关,是不是有点烦?尤其手机端,戳那个小叉子贼费劲。其实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且后来居上。