跳转到内容

自定义事件封装 ​

刀刀
0字
0分钟
8/16/2026
vuecustom-directiveintersectionobserveranimationweb-api
封装 Vue 自定义事件,利用 IntersectionObserver 监听元素视口可见性,结合 Web Animation API 实现元素平滑上升动画。

外部资源引用

▸
实现元素平滑上升
渡一教育 视频

元素平滑上升 ​

Vue 项目中通过封装自定义事件,实现盒子滚动到视口内时平滑上升的效果。

前置知识 ​

  1. Element.animate() :创建一个新的 Animation 对象并应用于元素,运行动画。返回一个新建的 Animation 对象实例。

    接收两个参数:

    • 第一个参数:描述动画的 Keyframes 数组对象,包含一个或多个 CSS 样式和时间。
    • 第二个参数:描述动画的选项对象,包含动画属性,如持续时间 duration、延迟时间 delay、动画效果 ease 等。

    返回的动画对象实例可控制动画的暂停、播放、取消等操作。调用对应的 pause()、play()、cancel() 方法即可。

  2. [IntersectionObserver] :异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法,设计为异步以避免阻塞主线程。一个 IntersectionObserver 对象被创建后配置不可更改,但可在同一个观察者对象中配置监听多个目标元素。

思路 ​

  1. 创建自定义事件,在 mounted 组件挂载生命周期钩子上,使用 Web Animation API 应用动画。写法为 el.animate(关键帧, 配置)。组件挂载后直接触发动画。
  2. 先让动画暂停,使用 IntersectionObserver 监听元素是否出现在视口内,出现则播放动画。在 unmounted 组件卸载生命周期钩子上,取消对元素的监听。
  3. 在 mounted 生命周期钩子添加判断,如果元素已在视口内,则 return 不播放动画。
  4. 声明一个 WeakMap 变量 map,存储未播放过动画的元素,播放后从 map 内删除,避免重复播放。

代码实现 ​

js
const DISTANCE = 100 // 初始y轴偏移位置
const DURATION = 500 // 动画持续时间
const map = new WeakMap()

// IntersectionObserver API
const ob = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      // 出现在视口中
      console.dir(entry)
      const animation = map.get(entry.target)
      animation && animation.play()
      ob.unobserve(entry.target) // 播放过后就不再播放动画
    }
  }
})

const isBelowViewPort = (el) => {
  const rect = el.getBoundingClientRect()
  return rect.top - window.innerHeight > 0
}

export default {
  mounted(el, binding) {
    // 判断当前元素是否在视口之上或者视口内,在的话不需要播放动画
    if (!isBelowViewPort(el)) return

    // 元素挂载后使用 Web Animation API 来应用动画。写法为el.animate(关键帧, 配置)
    const animation = el.animate(
      [
        {
          transform: `translateY(${DISTANCE}px)`,
          opacity: 0.5,
        },
        {
          transform: `translateY(0)`,
          opacity: 1,
        },
      ],
      {
        duration: DURATION,
        ease: 'ease-out',
        fill: 'forwards',
      },
    )

    animation.pause() // 先暂停动画,等待指令的触发
    map.set(el, animation)
    ob.observe(el) // 观察元素是否进入视口
  },
  unmounted(el) {
    ob.unobserve(el) // 元素卸载后断开观察
  },
}

总体效果 ​

跳转预览:点击跳转

贡献者

The avatar of contributor named as duyidao duyidao
The avatar of contributor named as v_duyilin v_duyilin
The avatar of contributor named as 刀刀 刀刀

页面历史

刀刀博客累计访客 人;文档累计访问量共 次