在大屏可视化项目中,数字是核心信息载体 —— 无论是销售额、用户数还是业务指标,单纯静态展示会显得单调,而「数字滚动增长」的动态效果能显著提升视觉吸引力,让数据变化更直观。
本文基于 Vue + vue-animate-number,结合大屏场景需求(千分位分隔、自定义小数位、适配大屏视觉),封装可复用的数字滚动组件,代码可直接落地,还会详解格式化工具函数原理和常见问题解决方案。
一、大屏数字组件的核心需求
在封装组件前,先明确大屏场景下数字展示的特殊要求,避免功能冗余或缺失:
- 动态滚动:数字从 0 平滑增长到目标值,而非瞬间切换,符合大屏 “数据加载中” 的交互预期;
- 格式规范:支持千分位分隔(如
123456→123,456),避免大数字阅读困难; - 小数控制:不同指标需不同小数位(如金额保留 2 位,数量保留 0 位),支持动态配置;
- 稳定兼容:数字更新时能重新触发滚动,避免因数据重复导致动画失效;
- 视觉适配:配合大屏自适应(如 autofit 缩放),字体和间距保持清晰可辨。
二、组件封装:从依赖到完整实现
第一步:安装核心依赖
组件基于 vue-animate-number 实现滚动动画(轻量级库,专门处理数字过渡),先安装依赖:
# npm 安装
npm i vue-animate-number -S
# pnpm 安装(推荐)
pnpm add vue-animate-number -S
第二步:封装格式化工具函数
大屏数字需统一格式(千分位、小数位),单独抽离工具函数(如 @/unit/index.js),方便复用和维护:
// utils.js:数字格式化工具
/**
* 千分位分隔函数
* @param {String/Number} nStr - 待格式化的数字(支持字符串或数字类型)
* @return {String} 带千分位分隔的数字字符串
*/
export function addCommas(nStr) {
// 处理非数字情况
if (nStr === null || nStr === undefined || isNaN(Number(nStr))) return '0';
// 转为字符串,拆分整数和小数部分
nStr = String(nStr);
const x = nStr.split('.');
let x1 = x[0]; // 整数部分
const x2 = x.length > 1 ? '.' + x[1] : ''; // 小数部分(可选)
// 正则匹配:每 3 位数字加一个逗号(从右往左)
const rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
// 替换规则:$1 是匹配到的前半部分,$2 是末尾 3 位,中间加逗号
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
// 拼接整数和小数部分,返回结果
return x1 + x2;
}
/**
* 数字格式化主函数(整合千分位+小数位控制)
* @param {String/Number} val - 原始数字
* @param {Number} num - 保留小数位数(默认 0)
* @return {String} 格式化后的数字字符串
*/
export function formatter(val, num = 0) {
// 处理空值/非数字,返回 0
if (!val || isNaN(Number(val))) return '0';
// 1. 保留指定小数位(toFixed 会自动四舍五入)
const fixedVal = parseFloat(val).toFixed(num);
// 2. 加上千分位分隔
return addCommas(fixedVal);
}
工具函数原理解读:
-
addCommas千分位实现:用正则
/(\d+)(\d{3})/匹配 “1 位以上数字 + 末尾 3 位数字”,循环替换直到无法匹配(如1234567→ 先替换为123,4567,再替换为1,234,567),兼容整数和带小数的数字。 -
formatter整合逻辑:先通过
toFixed(num)控制小数位(避免手动计算四舍五入),再调用addCommas加千分位,确保格式统一。
第三步:封装数字滚动组件(NNum.vue)
基于 vue-animate-number 封装通用组件,支持传入目标数字、小数位数,内置格式化逻辑:
<template>
<!--
vue-animate-number 核心属性:
- from:起始值(固定 0,实现从 0 滚动增长)
- to:目标值(父组件传入的 data)
- duration:动画时长(3秒,大屏场景下节奏适中)
- formatter:自定义格式化函数(对接 utils 中的 formatter)
- key:监听 data 变化,确保数据更新时重新触发动画
-->
<animate-number
class="n-num"
:from="0"
:to="Number(data)" <!-- 转为数字类型,避免字符串导致动画异常 -->
:duration="duration"
:formatter="(val) => formatter(val, dot)"
:key="data + dot" <!-- 数据或小数位变化时,重新初始化组件 -->
/>
</template>
<script>
// 引入依赖和工具函数
import VueAnimateNumber from 'vue-animate-number';
import { formatter } from '@/unit';
// 全局注册 vue-animate-number(或在组件内局部注册)
Vue.use(VueAnimateNumber);
export default {
name: 'NNum',
props: {
//......可拓展
/**
* 目标数字(支持字符串/数字类型,适配后端返回数据格式)
* 示例:123456 或 "123456.78"
*/
data: {
type: [String, Number],
default: 0,
// 校验:确保是有效数字(空值或非数字时重置为 0)
validator: (value) => {
return value === '' || value === null || value === undefined
? true
: !isNaN(Number(value));
}
},
/**
* 保留小数位数(默认 0,即整数)
* 示例:0 → 123,456;2 → 123,456.78
*/
dot: {
type: Number,
default: 0,
// 校验:小数位不能为负数
validator: (value) => value >= 0
}
},
data() {
return {
// 动画时长(3秒,大屏场景下既不拖沓也不仓促,可通过 props 扩展为可配置)
duration: 3000
};
},
methods: {
// 注入格式化函数(避免在模板中直接写复杂逻辑)
formatter
}
};
</script>
<style scoped lang="less">
/* 大屏数字样式:适配大屏视觉,支持自定义颜色和字体 */
.n-num {
/* 大屏常用字体:粗体、无衬线,提升远距离可读性 */
font-family: 'DINPro-Medium', 'SourceHanSansCN-Bold', sans-serif;
font-weight: 700;
/* 字体大小可通过父组件传入 class 覆盖,适配不同模块 */
font-size: 24px;
/* 大屏数字常用颜色:白色/浅蓝色,配合深色背景 */
color: #fff;
/* 避免数字换行(大屏数字通常单行展示) */
white-space: nowrap;
}
</style>
组件核心设计亮点:
- 类型兼容:
data支持字符串 / 数字类型,适配后端返回的 “数字字符串”(如"123456"),避免类型转换错误; - 动画重置:
key="data + dot"确保 “数据更新” 或 “小数位变化” 时,组件重新初始化,动画再次触发(解决vue-animate-number数据重复时动画不生效的问题); - 参数校验:通过
validator校验data和dot,避免传入负数小数位或非数字,提升组件稳定性; - 样式可扩展:字体大小、颜色通过父组件 class 覆盖,适配大屏不同模块(如标题区大数字、列表区小数字)。
三、实战使用:在大屏项目中集成组件
封装完成后,在大屏页面中引入 NNum 组件,根据不同业务场景配置参数,配合 autofit 自适应效果:
示例 1:展示整数指标(如用户数)
<template>
<div class="dashboard-module">
<div class="module-title">总用户数</div>
<!-- 整数:dot 设为 0,数字从 0 滚动到 123456 -->
<n-num
class="module-value"
:data="totalUserCount"
:dot="0"
/>
</div>
</template>
<script>
import NNum from '@/components/NNum.vue';
export default {
components: { NNum },
data() {
return {
totalUserCount: 123456 // 实际项目中从接口获取
};
}
};
</script>
<style scoped lang="less">
.dashboard-module {
padding: 20px;
background: rgba(0, 30, 80, 0.5);
border-radius: 8px;
.module-title {
font-size: 18px;
color: #8cb7ff;
margin-bottom: 10px;
}
.module-value {
font-size: 36px; // 标题区大数字,覆盖组件默认字体大小
color: #40c4ff; // 自定义颜色,突出关键指标
}
}
</style>
示例 2:展示小数指标(如销售额)
<template>
<div class="sales-module">
<div class="module-title">本月销售额(元)</div>
<!-- 小数:dot 设为 2,保留 2 位小数 -->
<n-num
class="module-value"
:data="monthlySales"
:dot="2"
/>
</div>
</template>
<script>
import NNum from '@/components/NNum.vue';
export default {
components: { NNum },
data() {
return {
monthlySales: 987654.32 // 实际项目中从接口获取,支持字符串如 "987654.32"
};
}
};
</script>
<style scoped lang="less">
.sales-module {
padding: 20px;
background: rgba(0, 40, 100, 0.5);
border-radius: 8px;
.module-title {
font-size: 18px;
color: #8cb7ff;
margin-bottom: 10px;
}
.module-value {
font-size: 32px;
color: #ffd740; // 金额用黄色,符合大屏视觉习惯
}
}
</style>
示例 3:动态更新数字(如实时数据)
若数字从接口实时获取(如每秒更新的在线人数),组件会自动监听 data 变化,重新触发滚动动画:
<template>
<div class="realtime-module">
<div class="module-title">当前在线人数</div>
<n-num
class="module-value"
:data="onlineCount"
:dot="0"
/>
</div>
</template>
<script>
import NNum from '@/components/NNum.vue';
export default {
components: { NNum },
data() {
return {
onlineCount: 0
};
},
mounted() {
// 模拟实时接口请求:每秒更新一次在线人数
this.timer = setInterval(() => {
// 生成 10000-15000 之间的随机数,模拟实时变化
this.onlineCount = Math.floor(Math.random() * 5000) + 10000;
}, 10000);
},
beforeDestroy() {
// 清除定时器,避免内存泄漏
clearInterval(this.timer);
}
};
</script>
四、问题解决:大屏场景下的常见坑点
在实际使用中,可能会遇到一些适配问题,这里整理 3 个高频场景的解决方案:
1. 数字更新时动画不触发
问题:实时数据更新时(如 onlineCount 从 12345 变为 12346),组件未重新滚动。
原因:vue-animate-number 检测到 to 值变化较小时,可能不会触发动画;或 key 未正确监听数据变化。
解决方案:
-
确保
key包含data(如key="data + dot"),数据变化时key同步更新,组件重新初始化; -
若变化幅度极小(如 ±1),可在更新前将
data设为 “当前值 - 10” 再更新到目标值,强制触发动画:updateOnlineCount(newVal) { this.onlineCount = newVal - 10; // 先设为稍小的值 this.$nextTick(() => { this.onlineCount = newVal; // 再更新到目标值,触发滚动 }); }
2. 超大数字格式化(如万 / 亿级)
问题:数字超过千万级(如 123456789),千分位分隔后仍显冗长,大屏展示不够简洁。
解决方案:扩展 formatter 函数,支持 “万 / 亿” 单位转换:
// utils.js 中扩展 formatter 函数
export function formatter(val, num = 0, unit = false) {
if (!val || isNaN(Number(val))) return '0';
let fixedVal = parseFloat(val);
let unitStr = '';
// 若开启单位转换,处理万/亿级数字
if (unit) {
if (fixedVal >= 100000000) { // 亿级
fixedVal = fixedVal / 100000000;
unitStr = '亿';
} else if (fixedVal >= 10000) { // 万级
fixedVal = fixedVal / 10000;
unitStr = '万';
}
}
// 保留小数位 + 千分位分隔 + 拼接单位
return addCommas(fixedVal.toFixed(num)) + unitStr;
}
使用时开启 unit: true:
<n-num
:data="123456789"
:dot="1"
:unit="true" <!-- 新增 unit props,控制是否显示单位 -->
/>
<!-- 输出结果:1.2 亿 -->
五、组件扩展:让功能更贴合大屏需求
根据实际业务,可进一步扩展组件功能,提升大屏适配性:
- props添加单位插槽:支持在数字后添加自定义单位(如
元、人、次),需修改组件代码:
2. 空状态处理:添加 empty-text props,数据为空时显示自定义文本(如 --),而非默认 0:
<template>
<animate-number v-if="data !== '' && data !== null" ...>
</animate-number>
<span v-else class="empty-text">{{ emptyText }}</span>
</template>
<script>
export default {
props: {
emptyText: {
type: String,
default: '--'
}
}
};
</script>
总结
大屏数字滚动组件的核心是「动态效果 + 格式规范 + 大屏适配」,本文封装的 NNum 组件具备以下优势:
- 易用性:只需传入
data和dot,无需关心动画和格式化细节; - 稳定性:参数校验、类型兼容、动画重置,避免大屏场景下的异常;
- 可扩展性:支持单位、空状态、等自定义,适配不同业务需求。
这套方案已在多个生产级大屏项目中落地(如业务监控大屏、领导驾驶舱),能有效提升数字展示的视觉冲击力和可读性。你在大屏数字展示中还遇到过哪些需求?欢迎在评论区分享~