如何实现元素随滚动平滑上升
#技术栈Vue3+TypeScript#
相比大家没少见过这个的效果:
作为视觉效果是很不错的 同时实现也很简单,本质是封装一个Vue指令
1,创建指令文件 src / directives / vSlidenIn.ts
import type { Directive } from 'vue'
const vSlideIn: Directive = {
mounted(element) {
// 组件记载后会触发的逻辑
}
}
export default vSlideIn
2,Animation API 添加入场动画
同学们可能会想到改变stye或者class类,这些都是偏麻烦的,入侵性太大的指令泛用性会很低。我们这里使用Animation API封装动画,保证复用性
const MOVE_DISTANCE = 8
const MOVE_DURATION = 1000
......
const vSlideIn: Directive = {
mounted(element) {
const animation = element.animate([
{
transform: `translateY(${MOVE_DISTANCE}rem)`,
opacity: 0,
},
{
transform: 'translateY(0)',
opacity: 1,
},
], {
duration: MOVE_DURATION,
easing: 'ease'
})
animation.pause()
}
}
.......
这样是实现了动画,但动画会在所有组件出现时执行---那该如何让每个元素被滑动到时执行呢?请问下面
3,Intersection Observer API 判断执行时机
调用其中的IntersectionObserver去监听使用该指令的元素,通过内置返回的元素的isInterscting值判断元素是否与窗口重叠
......
const ob = new IntersectionObserver(entries => {
// entries是一起监听的所有元素,是数组
for (const entry of entries) {
if (entry.isIntersecting){
const element = entry.target
// 执行动画逻辑
}
}
})
......
const vSlideIn: Directive = {
mounted(element) {
const animation = element.animate([
{
transform: `translateY(${MOVE_DISTANCE}rem)`,
opacity: 0,
},
{
transform: 'translateY(0)',
opacity: 1,
},
], {
duration: MOVE_DURATION,
easing: 'ease'
})
ob.observe(element) // 监听
},
unmounted(element) {
ob.unobserve(element) // 记得组件销毁后取消监听
}
}
但是,如何让每个元素和执行的动画对上号呢?
4,New Map存放映射
在元素初始化时 暂停后动画pause() 全局new一个WeakMap来存放元素及其对应的动画 在监听函数中调用map找到对应的animation进行执行
// 滑动入场指令
import type { Directive } from 'vue'
const MOVE_DISTANCE = 8
const MOVE_DURATION = 1000
const animationMap = new WeakMap()
const ob = new IntersectionObserver(entries => {
// entries是一个重叠状态的一个数组
for (const entry of entries) {
if (entry.isIntersecting){
const element = entry.target
const animation = animationMap.get(element)
if (animation) {
animation.play()
ob.unobserve(element)
}
}
}
})
const vSlideIn: Directive = {
mounted(element) {
const animation = element.animate([
{
transform: `translateY(${MOVE_DISTANCE}rem)`,
opacity: 0,
},
{
transform: 'translateY(0)',
opacity: 1,
},
], {
duration: MOVE_DURATION,
easing: 'ease'
})
animation.pause()
animationMap.set(element, animation)
ob.observe(element)
},
unmounted(element) {
ob.unobserve(element)
}
}
export default vSlideIn
5,组件的应用
开袋即食,引入后在元素内加入v-slide-in即可,Vue会自己编译的
<template>
<main class="space-y-10">
<article v-for="i in 10" :key="i" class="w-100 h-50 bg-red-500 shadow-md text-center leading-50 mx-auto text-white text-2xl" v-slide-in>
<h1>测试{{ i }}</h1>
</article>
</main>
</template>
<script setup lang="ts">
import vSlideIn from '@/directives/vSlideIn'
</script>