大屏数据展示必备!零依赖、高性能的数字动画解决方案
上周接了个需求需要在项目中,使用数字的动态变化效果。本文将介绍如何使用 Vue 3 实现一个高性能的数字动态滚动组件,让你的数据展示更加生动有趣。
为什么需要数字翻牌器?
数字翻牌器(Count Flop)是一种常见的UI效果,特别适用于:
-
数据大屏展示
-
实时统计指标
-
金融数据变化
-
游戏分数更新
-
计数器应用
相比静态数字,动态翻牌效果能更好地吸引用户注意力,增强数据变化的感知。
核心功能实现
1. 数字格式化与拆分
javascript
const formatNumber = (num: number | string): string[] => {
return num
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
.split("");
};
这个方法实现了:
-
将数字转为字符串
-
添加千分位分隔符
-
拆分为单个字符数组
2. 变化检测与动画触发
javascript
watch(() => props.val, newVal => {
const newValueArray = formatNumber(newVal);
const oldValueArray = previousValue.value;
changedIndexes.value = [];
newValueArray.forEach((digit, index) => {
if (digit !== "," && (index >= oldValueArray.length || digit !== oldValueArray[index])) {
changedIndexes.value.push(index);
}
});
// 更新显示值
displayValue.value = newValueArray;
previousValue.value = newValueArray;
}, { immediate: true });
这段代码实现了:
-
监听数值变化
-
比较新旧值差异
-
标记需要动画的数字位置
-
只更新变化的部分,提升性能
3. CSS 动画实现
css
.count-flop-box {
position: relative;
display: inline-block;
overflow: hidden;
height: 100%;
}
.count-flop-content {
position: absolute;
left: 0;
top: 0;
width: 100%;
animation-fill-mode: forwards !important;
}
.rolling_0 {
animation: rolling_0 2.1s ease;
}
@keyframes rolling_0 {
from { transform: translateY(-90%); }
to { transform: translateY(0); }
}
动画原理:
-
每个数字容器设置为相对定位
-
数字列表使用绝对定位
-
通过 transform: translateY 控制显示位置
-
为每个数字定义独立的动画效果
完整组件代码
html
<template>
<div :style="countflop" style="display: inline-block">
<div
class="count-flop-box"
:style="countflopbox"
v-for="(item, index) in displayValue"
:key="index"
>
<div
v-if="item !== ','"
class="count-flop-content"
:class="getDigitClass(item, index)"
>
<div
v-for="(item2, index2) in numberList"
:key="index2"
class="count-flop-num"
>
{{ item2 }}
</div>
</div>
<div v-else class="count-flop-content">,</div>
</div>
<div v-if="suffix" class="count-flop-unit">{{ suffix }}</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, defineProps } from "vue";
const props = defineProps({
val: {
type: [Number, String],
default: 0
},
suffix: {
type: String,
default: ""
},
countflop: {
type: String,
default: ""
},
countflopbox: {
type: String,
default: ``
}
});
const displayValue = ref<string[]>([]);
const previousValue = ref<string[]>([]);
const numberList = ref<number[]>([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
const changedIndexes = ref<number[]>([]);
const formatNumber = (num: number | string): string[] => {
return num
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
.split("");
};
const getDigitClass = (digit: string, index: number) => {
if (digit === ",") return "";
return changedIndexes.value.includes(index) ? `rolling_${digit}` : `static_${digit}`;
};
watch(
() => props.val,
newVal => {
const newValueArray = formatNumber(newVal);
const oldValueArray = previousValue.value;
changedIndexes.value = [];
newValueArray.forEach((digit, index) => {
if (digit !== "," && (index >= oldValueArray.length || digit !== oldValueArray[index])) {
changedIndexes.value.push(index);
}
});
displayValue.value = newValueArray;
previousValue.value = newValueArray;
},
{ immediate: true }
);
</script>
<style scoped>
/* 样式部分与上面相同 */
</style>
组件使用示例
html
<CountFlop
:val="currentValue"
suffix="%"
countflop="font-size: 24px; color: #42b883;"
countflopbox="width: 20px; height: 30px; background: rgba(66, 184, 131, 0.1); border-radius: 4px; margin: 0 2px;"
/>
参数说明
性能优化技巧
-
局部更新:只对变化的数字应用动画,避免不必要的重绘
-
CSS硬件加速:使用 transform 属性触发GPU加速
-
动画复用:预定义所有数字的动画效果
-
轻量级DOM:最小化DOM节点数量