Skip to content

自定义事件封装

作者:Yuan Tang
更新于:5 个月前
字数统计:818 字
阅读时长:2 分钟

元素平滑上升

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

前值知识

  1. `Element.animate()`
    https://developer.mozilla.org/zh-CN/docs/Web/API/Element/animate
    :可以创建一个新的 Animation 的便捷方法,将它应用于元素,然后运行动画。返回一个新建的 Animation 对象实例。

    接收两个参数:

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

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

  2. [IntersectionObserver] :是一个异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法,它被设计为异步的,以避免阻塞主线程。当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

思路分析

  1. 创建一个自定义事件,在 mounted 组件挂载的生命周期钩子上,使用 Web Animation API 来应用动画。写法为el.animate(关键帧, 配置)。此时全部的组件挂载完毕后直接触发了动画。
  2. 先让动画暂停,使用 IntersectionObserver 监听元素是否出现在视口内,如果出现则播放动画。在 unmounted 组件卸载的生命周期钩子上,取消对元素的监听。此时组件无论一开始有没有出现在视口,都会触发一次动画。
  3. mounted 生命周期钩子函数添加一个判断,如果此时元素已经出现在视口内,则 return 不播放动画。
  4. 声明一个变量 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) // 播放过后就不再播放动画
    }
  }
})

function 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) // 元素卸载后断开观察
  }
}

总体效果

Contributors

Yuan Tang