0 滚动动画?
一些好看的页面滚动动画实现,都是根据当前滚动的位置,逐渐“播放”动画。并不是随着时间播放,是随着滚动条向下滚动而播放。
一般都会有多个动画,每个动画在某一个滚动区间内进行播放,超过区间后隐藏或者是滚动出当前视窗(利用sticky定位)。
于是就需要监听元素滚动的位置,实时计算当前动画的进度。
1 技术实现
为滚动父元素添加滚动事件监听,实时计算每一个动画的进度(可以映射到透明度、大小、位置等的变化)。每一个动画可以包含这样一些属性:
{
startY:开始动画的坐标
endY:结束动画的坐标
startVal:动画进度起始值
endVal:动画进度结束值
currVal/updateCurrVal:当前动画进度值/当前动画更新函数
}
当父元素滚动时,可以去循环这样一个动画列表,逐个计算当前动画进度,并通过他自身的属性更新对应的动画进度。
结合Vue3 特性
利用Vue3的响应式以及自定义指令:将动画对象的当前进度值设置为一个响应式数据,滚动的时候直接更新其值可以同步更新视图;通过自定义指令作用于滚动父元素,将动画对象列表传入,通过指令绑定滚动、更新每一个动画值。同时,自定义指令的方式,可以实现多场景复用。
demo.vue
<script setup lang="ts">
import { ref } from "vue";
import {vScrollIndex} from '../your/path/scrollIndex'
const anm0 = ref(0)
const anm1 = ref(0)
const anm2 = ref(1)
const anm3 = ref(0)
const options = [
{startY: 10, endY: 400, startVal: 0, endVal: 1, bind: anm0},
{startY: 500, endY: 800, startVal: -10, endVal: 16, bind: anm1},
{startY: 900, endY: 1300, startVal: 1, endVal: 10, bind: anm2},
{startY: 1400, endY: 1600, startVal: 20, endVal: 60, bind: anm3}
]
</script>
<template>
<main>
<div class="show" title="动画值更新展示区">
<div class="item">
0: {{ anm0 }}
</div>
<div class="item">
1: {{ anm1 }}
</div>
<div class="item">
2: {{ anm2 }}
</div>
<div class="item">
3: {{ anm3 }}
</div>
</div>
<div class="wrap" v-scroll-index="{options}" title="滚动父元素">
<div class="item" v-for="idx in 40" :key="idx">
{{ String.fromCharCode(30 + idx).repeat(30) }}
</div>
</div>
</main>
</template>
<style lang="scss">
.wrap {
height: 800px;
overflow-y: scroll;
.item{
margin: 20px;
}
}
</style>
vScrollIndex.ts
import type { Directive, DirectiveBinding, Ref } from "vue";
interface IOption{
startY: number
endY: number
startVal: number
endVal: number
bind: Ref<number>
}
export interface ScrollIndexBinding{
options: Array<IOption>
[key: string | number]: any
}
let updateAll
function handleScroll(options: IOption[]){
function updateArr(event: Event){
const scrollTop = (event.target as HTMLElement).scrollTop;
options.forEach(item => {
const { startY, endY, startVal, endVal, bind } = item;
if(scrollTop <= startY){
bind.value = startVal
}else if(scrollTop >= endY){
bind.value = endVal
}else{
bind.value = (scrollTop - startY) / (endY - startY) * (endVal - startVal);
}
})
}
return updateArr
}
export const vScrollIndex: Directive = {
mounted: (el:Element, binding: DirectiveBinding<ScrollIndexBinding>) => {
const { options } = binding.value;
updateAll = handleScroll(options)
el.addEventListener('scroll', updateAll)
},
unmounted: (el) => {
el.removeEventListener('scroll', updateAll!)
}
}