最近在做自己的大屏项目,毕竟很多公司还是要求提交自己的作品的。
虽然做的效果一般,但是UI和UE上的体验一定要拉满,给面试官以视觉上的冲击力才是大屏的关键。
在掘金上看到这篇文章写的仿百度数字滚动效果,打算比着葫芦画瓢,复刻一个Vue3版本的数字滚动组件。
正好现在一直在用 TRAE,复刻代码也比较简单。
效果
数字滚动组件的核心部分就是延迟滚动效果,各个数字从前到后延迟滚动会造成一种非常好的视觉效果。
我一开始以为直接用CSS实现就齐活了,后来发现事情不是这么简单的。
- 每位数字的滚动时间不同,做出前后延迟效果。
- 数字需要精准的滚动到所需的内容,并且滚动过程需要连贯。
- 滚动效果上带有一种惯性的感觉。

本来想着自己比着来一遍速度也很快,但是低估了内容的难度。
于是乎我打开 TRAE ,将J友的代码拷进去,输入指令:
请帮我将上述代码转换为Vue3的组件代码,我要实现的是一个数字滚动的组件,将每个数字进行分割,最大8位数,不足8位使用0补齐8位。同时每3位增加一个逗号分隔符,每位数字都拥有自己的背景,类似于记分牌的效果。
1,2,3.....
于是乎以下的代码就输出了出来!
实现代码
html
<div class="num-statistic">
<div class="num-statistic-content">
<template v-for="(_digit, index) in numList" :key="index">
<div class="digit-container" :class="`digit-container${index+1}`" ref="digitListRefs">
<div class="digit-list">
<div v-for="(_item, idx) in 10" :key="idx" class="digit">{{ idx }}</div>
<div v-for="(_item, idx) in 10" :key="`dup-${idx}`" class="digit">{{ idx }}</div>
<div v-for="(_item, idx) in 10" :key="`dup2-${idx}`" class="digit">{{ idx }}</div>
</div>
</div>
<div v-if="index === 1 || index === 4" class="num-split" :key="`split-${index}`">,</div>
</template>
</div>
</div>
Ps: TRAE提醒我,每个需要滚动的数字组件一定要通过 v-for 循环创建,即便你的数字位数是固定的。
因为Vue3中只有通过v-for定义相同的ref才会被归纳到一个数组中。
单纯的写多个div其ref相同的话,并不是数组,而是一个对象,指向最后一个DOM。
js
const props = defineProps<{
num: number
}>()
const digitListRefs = ref<HTMLDivElement[]>([])
const numList = computed(() => {
const numStr = String(props.num).padStart(8, '0');
return numStr.split('');
});
watch(numList, () => {
nextTick(() => {
startAnimate();
})
}, {
immediate: true
})
const startAnimate = () => {
const digits = numList.value;
digitListRefs.value.forEach((element, i) => {
if (element && element.querySelector('.digit-list')) {
const list = element.querySelector('.digit-list') as HTMLElement;
const targetDigit = parseInt(digits[i], 10);
const targetY = -(20 + targetDigit) * 50;
list.style.transform = `translateY(${targetY}px)`;
}
});
}
Ps: 这里注意,我设置的数字最大8位数,逻辑上是不足8位以0补全。
完整组件代码
html
<script setup lang="ts">
import {ref, defineProps, computed, watch, nextTick} from "vue";
const props = defineProps<{
num: number
}>()
const digitListRefs = ref<HTMLDivElement[]>([])
const numList = computed(() => {
const numStr = String(props.num).padStart(8, '0');
return numStr.split('');
});
watch(numList, () => {
nextTick(() => {
startAnimate();
})
}, {
immediate: true
})
const startAnimate = () => {
const digits = numList.value;
digitListRefs.value.forEach((element, i) => {
if (element && element.querySelector('.digit-list')) {
const list = element.querySelector('.digit-list') as HTMLElement;
const targetDigit = parseInt(digits[i], 10);
const targetY = -(20 + targetDigit) * 50;
list.style.transform = `translateY(${targetY}px)`;
}
});
}
</script>
<template>
<div class="num-statistic">
<div class="num-statistic-content">
<template v-for="(_digit, index) in numList" :key="index">
<div class="digit-container" :class="`digit-container${index+1}`" ref="digitListRefs">
<div class="digit-list">
<div v-for="(_item, idx) in 10" :key="idx" class="digit">{{ idx }}</div>
<div v-for="(_item, idx) in 10" :key="`dup-${idx}`" class="digit">{{ idx }}</div>
<div v-for="(_item, idx) in 10" :key="`dup2-${idx}`" class="digit">{{ idx }}</div>
</div>
</div>
<div v-if="index === 1 || index === 4" class="num-split" :key="`split-${index}`">,</div>
</template>
</div>
</div>
</template>
<style scoped lang="less">
.num-statistic-content {
display: flex;
gap: 12px;
}
.digit-container1,
.digit-container2,
.digit-container3,
.digit-container4,
.digit-container5,
.digit-container6,
.digit-container7,
.digit-container8 {
width: 36px;
height: 50px;
text-align: center;
line-height: 50px;
overflow: hidden;
background-color: #244193;
font-size: 24px;
font-weight: bold;
border-radius: 4px;
.digit {
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
}
.digit-container1 .digit-list {
transition: transform 1720ms ease-in-out;
}
.digit-container2 .digit-list {
transition: transform 1760ms ease-in-out;
}
.digit-container3 .digit-list {
transition: transform 1800ms ease-in-out;
}
.digit-container4 .digit-list {
transition: transform 1840ms ease-in-out;
}
.digit-container5 .digit-list {
transition: transform 1880ms ease-in-out;
}
.digit-container6 .digit-list {
transition: transform 1920ms ease-in-out;
}
.digit-container7 .digit-list {
transition: transform 1960ms ease-in-out;
}
.digit-container8 .digit-list {
transition: transform 2000ms ease-in-out;
}
.num-split {
width: 20px;
text-align: center;
font-size: 24px;
font-weight: bold;
line-height: 50px;
}
</style>
最后非常感谢这位兄弟@三个木base的思路及源码,再次感谢!