创建v-number-scroll.js文件
javascript
v-number-scroll.js代码
export default {
install(Vue) {
// 格式化数字显示
function formatNumber(num, options) {
// 处理NaN情况
if (isNaN(num)) return '0';
// 保留指定小数位数,解决精度问题
const fixedNum = Number(num.toFixed(options.decimalPlaces));
// 添加千位分隔符
const parts = fixedNum.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
const formatted = parts.join('.');
// 添加前缀和后缀
return `${options.prefix}${formatted}${options.suffix}`;
}
// 开始数字滚动动画
function startAnimation(el) {
// 停止当前动画
if (el._numberScroll && el._numberScroll.animationFrameId) {
cancelAnimationFrame(el._numberScroll.animationFrameId);
}
const scrollData = el._numberScroll;
if (!scrollData) return;
const { currentValue, targetValue, options, easingFunctions } = scrollData;
const startValue = currentValue;
const difference = targetValue - startValue;
const startTime = performance.now();
const duration = options.duration;
const easingFunc = easingFunctions[options.easing] || easingFunctions.linear;
// 动画函数
const animate = (currentTime) => {
const elapsedTime = currentTime - startTime;
const progress = Math.min(elapsedTime / duration, 1);
const easedProgress = easingFunc(progress);
// 计算当前值
scrollData.currentValue = startValue + difference * easedProgress;
// 更新显示
el.textContent = formatNumber(scrollData.currentValue, options);
// 继续动画或结束
if (progress < 1) {
scrollData.animationFrameId = requestAnimationFrame(animate);
} else {
// 确保最终值准确
scrollData.currentValue = targetValue;
el.textContent = formatNumber(targetValue, options);
scrollData.animationFrameId = null;
}
};
// 开始动画
scrollData.animationFrameId = requestAnimationFrame(animate);
}
Vue.directive('number-scroll', {
/**
* 指令绑定到元素时调用
*/
bind(el, binding) {
// 初始化配置
const defaults = {
duration: 1000,
easing: 'easeOutQuad',
decimalPlaces: 0,
// 数字前缀:显示在数字前面的字符
// 可用于添加货币符号(如"$")、单位前缀等,默认为空
prefix: '',
// 数字后缀:显示在数字后面的字符
// 可用于添加单位(如"%""个""元")等,默认为空
suffix: ''
};
// 合并配置
const options = {
...defaults,
...(binding.value || {})
};
// 处理初始值
let initValue = 0;
if (binding.value && binding.value.initValue !== undefined) {
initValue = Number(binding.value.initValue) || 0;
}
// 存储当前值和动画状态
el._numberScroll = {
currentValue: initValue,
targetValue: initValue,
animationFrameId: null,
options,
// 缓动函数集合
easingFunctions: {
linear: (t) => t,
easeOutQuad: (t) => t * (2 - t),
easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
}
};
// 设置初始显示值
el.textContent = formatNumber(initValue, options);
},
/**
* 当绑定值更新时调用
*/
update(el, binding) {
// 检查是否有目标值
if (!binding.value || binding.value.target === undefined) return;
// 更新配置
if (binding.value && el._numberScroll) {
el._numberScroll.options = {
...el._numberScroll.options,
...binding.value
};
}
// 处理目标值
const targetValue = Number(binding.value.target);
if (isNaN(targetValue)) {
console.warn('v-number-scroll: 目标值必须是有效的数字');
return;
}
// 如果值没有变化,不执行动画
if (el._numberScroll && Math.abs(targetValue - el._numberScroll.targetValue) < 0.001) {
return;
}
// 更新目标值并开始动画
if (el._numberScroll) {
el._numberScroll.targetValue = targetValue;
startAnimation(el);
}
},
/**
* 指令与元素解绑时调用
*/
unbind(el) {
// 清除动画(使用传统判断方式替代可选链)
if (el._numberScroll && el._numberScroll.animationFrameId) {
cancelAnimationFrame(el._numberScroll.animationFrameId);
}
// 清除存储的属性
if (el._numberScroll) {
delete el._numberScroll;
}
}
});
}
};
在main.js种引入
javascript
import vNumberScroll from '@/util/v-number-scroll'; // 导入指令
// 注册数字滚动指令
Vue.use(vNumberScroll);
使用:
javascript
<span class="num1"
v-number-scroll="{
target: 1000, // 初始目标值
duration: 1500, // 动画时长
decimalPlaces: 2, // 0:整数,2:保留两位小数
}"
></span>