基于Vue实现卡片无限滚动动画
概要设计
设置css的animation在适当的时间点重置动画来现实视觉上无限滚动的效果。
详细设计
计算动画中所需预设的卡片dom节点个数(即视觉上最多能看到的卡片个数),如图1视窗高度为120px,卡片高度为56px,上下margin均为24px,因此一张卡片最多占据56+24*2=104px。当卡片处于视窗垂直居中的位置时,卡片上下各有(120-104)/2 = 8px的高度来承载其余卡片,因此所需预设卡片dom节点个数为3个。
图1
图2
设置动画的运动轨迹和持续时间来达到视觉上循环滚动的效果。如图3初始状态下第一张卡片位置处于距离视窗顶部24px的位置,当第二张卡片运动到距离视窗顶部24px的位置时重置动画即可达到视觉上连续滚动的效果。计算卡片需要向上滚动的公式为【初始距顶部距离 - 目标距顶部距离】即56+24*2-24=80px,因此设置动画帧的目标位置为transform: translateY(-80px)。
代码实现:
<template>
<div class="wrapper">
<div class="container">
<div class="content" v-for="i in 3" :key="i">
<img
class="image"
class="lazy" data-src="https://static-legacy.dingtalk.com/media/lADPDhmOwd9ZBQXNA8DNA8A_960_960.jpg"
alt=""
/>
<span>点五已获得500元京东卡</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup() {},
});
</script>
<style scoped>
.wrapper {
height: 500px;
display: flex;
align-items: center;
justify-content: center;
}
.container {
width: 300px;
height: 120px;
overflow: hidden;
background: #ccc;
}
.content {
width: 290px;
height: 56px;
display: flex;
margin: 24px auto;
background: #fff;
border-radius: 100px;
flex-flow: row nowrap;
align-items: center;
backdrop-filter: blur(4px);
animation: scroll 3s linear 1s infinite;
}
.image {
width: 36px;
height: 36px;
margin: auto 10px;
border-radius: 100%;
}
span {
font-family: PingFang SC;
font-size: 20px;
line-height: 20px;
text-align: center;
}
@keyframes scroll {
from {
transform: translateY(0);
}
to {
transform: translateY(-80px);
}
}
</style>
效果展示:
进阶功能
动态更新卡片的数据
功能分析
卡片仍保持无限滚动,但每张卡片展示的数据均不同,也就是说滚动时视窗内的dom节点不是每次都相同的。
概要设计
基于上述功能分析由此可以衍生出两个设计思路:
- 展示多少条数据就渲染多少个dom节点,简单粗暴无需考虑动态更新数据问题;当数据量大时会导致页面性能变差,除非数据量较少否则不推荐。
- 固定dom节点数,在dom节点移动到视窗之外即进行数据更新;需保证修改dom节点时不会导致视窗内的卡片闪烁;(采纳)
详细设计
1.根据当前视窗宽高以及卡片宽高,设置六个卡片dom节点,并将每三张卡片分一个块,这里分两个块为card1、card2(如下图)。
2.将两个块的位置重叠(为了方便设计动画),card2向上移动(56+24)*3=240px的距离。
3.计算card2动画需要延迟执行的时间。这里我设置每个card块动画移动距离为480px,执行时间为12s,那么card2延迟执行的时间公示为【延迟距离(card高度)/速度】即240/480/12 = 6s。所以动画帧设置为
@keyframes scroll1 {
from {
transform: translateY(0);
left: 0;
}
to {
transform: translateY(-480px);
}
}
@keyframes scroll2 {
from {
transform: translateY(-240px);
left: 0;
}
to {
transform: translateY(-720px);
}
}
.test1 {
animation: scroll1 12s linear 0s infinite;
margin-top: 120px;
}
.test2 {
animation: scroll2 12s linear 6s infinite;
transform: translateY(-240px);
}
4.监听card块,当card块移出视窗时,触发dom节点刷新。监听使用IntersectionObserver这个api。
@keyframes scroll1 {
from {
transform: translateY(0);
left: 0;
}
to {
transform: translateY(-480px);
}
}
@keyframes scroll2 {
from {
transform: translateY(-240px);
left: 0;
}
to {
transform: translateY(-720px);
}
}
.test1 {
animation: scroll1 12s linear 0s infinite;
margin-top: 120px;
}
.test2 {
animation: scroll2 12s linear 6s infinite;
transform: translateY(-240px);
}
完整代码
以下为完整代码:
<template>
<div class="wrapper">
<div class="container">
<div class="test1" id="card1">
<div class="content" v-for="item in cardArr1" :key="item">
<img class="image" class="lazy" data-src="https://static-legacy.dingtalk.com/media/lADPDhmOwd9ZBQXNA8DNA8A_960_960.jpg"
alt="" />
<span>
{{ item }}已获得500元京东卡
</span>
</div>
</div>
<div class="test2" id="card2">
<div class="content" v-for="item in cardArr2" :key="item">
<img class="image" class="lazy" data-src="https://static-legacy.dingtalk.com/media/lADPDhmOwd9ZBQXNA8DNA8A_960_960.jpg"
alt="" />
<span>
{{ item }}已获得100元京东卡
</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { ref, defineComponent, onMounted } from "vue";
export default defineComponent({
setup() {
let firsrObserver = true;
let data: any = [];
for (let i = 1; i <= 10; i++) {
data.push(i);
}
const cardArr1 = ref<Array<any>>(data.slice(0, 3));
const cardArr2 = ref<Array<any>>(data.slice(3, 6));
console.log(cardArr1.value, cardArr2.value);
let currentIndex = 6;
const getNewData = () => {
if (currentIndex + 3 <= data.length) {
return data.slice(currentIndex, (currentIndex = currentIndex + 3));
} else {
let rst = data.slice(currentIndex);
currentIndex = 3 - rst.length;
Array.prototype.push.apply(rst, data.slice(0, currentIndex));
return rst;
}
};
const observer2 = new IntersectionObserver((entry: any) => {
// 第三第四第五加载新数据
if (!entry[0].isIntersecting) {
console.log(entry);
if (entry[0].target.id === "card1") {
cardArr1.value.splice(0, 3, ...getNewData());
} else {
cardArr2.value.splice(0, 3, ...getNewData());
}
console.log(cardArr1.value, cardArr2.value);
}
firsrObserver = false;
});
onMounted(() => {
observer2.observe(document.getElementById("card1") as HTMLElement);
observer2.observe(document.getElementById("card2") as HTMLElement);
});
return {
data,
cardArr1,
cardArr2,
};
},
});
</script>
<style scoped>
.wrapper {
height: 500px;
display: flex;
align-items: center;
justify-content: center;
}
.container {
width: 300px;
height: 120px;
overflow: hidden;
background: #ccc;
}
.test1 {
animation: scroll1 12s linear 0s infinite;
margin-top: 120px;
}
.test2 {
animation: scroll2 12s linear 6s infinite;
transform: translateY(-240px);
}
.content {
width: 290px;
height: 56px;
display: flex;
margin: 24px auto;
background: #fff;
border-radius: 100px;
flex-flow: row nowrap;
align-items: center;
backdrop-filter: blur(4px);
}
.image {
width: 36px;
height: 36px;
margin: auto 10px;
border-radius: 100%;
}
span {
font-family: PingFang SC;
font-size: 20px;
line-height: 20px;
text-align: center;
}
@keyframes scroll1 {
from {
transform: translateY(0);
left: 0;
}
to {
transform: translateY(-480px);
}
}
@keyframes scroll2 {
from {
transform: translateY(-240px);
left: 0;
}
to {
transform: translateY(-720px);
}
}
</style>
效果展示:
以上就是基于Vue实现实现卡片无限滚动动画的详细内容,更多关于Vue无限滚动动画的资料请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341