最近在做自己的大屏项目,毕竟很多公司还是要求提交自己的作品的。

虽然做的效果一般,但是UI和UE上的体验一定要拉满,给面试官以视觉上的冲击力才是大屏的关键。

在掘金上看到这篇文章写的仿百度数字滚动效果,打算比着葫芦画瓢,复刻一个Vue3版本的数字滚动组件。

正好现在一直在用 TRAE,复刻代码也比较简单。

效果

数字滚动组件的核心部分就是延迟滚动效果,各个数字从前到后延迟滚动会造成一种非常好的视觉效果。

我一开始以为直接用CSS实现就齐活了,后来发现事情不是这么简单的。

  • 每位数字的滚动时间不同,做出前后延迟效果。
  • 数字需要精准的滚动到所需的内容,并且滚动过程需要连贯。
  • 滚动效果上带有一种惯性的感觉。

屏幕录制-2025-11-13-172657.gif

本来想着自己比着来一遍速度也很快,但是低估了内容的难度。

于是乎我打开 TRAE ,将J友的代码拷进去,输入指令:

请帮我将上述代码转换为Vue3的组件代码,我要实现的是一个数字滚动的组件,将每个数字进行分割,最大8位数,不足8位使用0补齐8位。同时每3位增加一个逗号分隔符,每位数字都拥有自己的背景,类似于记分牌的效果。

1,2,3.....

于是乎以下的代码就输出了出来!

实现代码

<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才会被归纳到一个数组中。

单纯的写多个divref相同的话,并不是数组,而是一个对象,指向最后一个DOM

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补全。

完整组件代码

<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的思路及源码,再次感谢!

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]