自定义事件封装
刀刀
0字
0分钟
2026/8/16
元素平滑上升
Vue 项目中通过封装自定义事件,实现盒子滚动到视口内时平滑上升的效果。
前置知识
Element.animate() :创建一个新的 Animation 对象并应用于元素,运行动画。返回一个新建的 Animation 对象实例。
接收两个参数:
- 第一个参数:描述动画的 Keyframes 数组对象,包含一个或多个 CSS 样式和时间。
- 第二个参数:描述动画的选项对象,包含动画属性,如持续时间
duration、延迟时间delay、动画效果ease等。
返回的动画对象实例可控制动画的暂停、播放、取消等操作。调用对应的
pause()、play()、cancel()方法即可。[
IntersectionObserver] :异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法,设计为异步以避免阻塞主线程。一个 IntersectionObserver 对象被创建后配置不可更改,但可在同一个观察者对象中配置监听多个目标元素。
思路
- 创建自定义事件,在
mounted组件挂载生命周期钩子上,使用 Web Animation API 应用动画。写法为el.animate(关键帧, 配置)。组件挂载后直接触发动画。 - 先让动画暂停,使用 IntersectionObserver 监听元素是否出现在视口内,出现则播放动画。在
unmounted组件卸载生命周期钩子上,取消对元素的监听。 - 在
mounted生命周期钩子添加判断,如果元素已在视口内,则return不播放动画。 - 声明一个
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) // 元素卸载后断开观察
},
}总体效果
跳转预览:点击跳转