vue自定义数字滚动插件
创建v-number-scroll.js文件
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种引入
import vNumberScroll from '@/util/v-number-scroll'; // 导入指令
// 注册数字滚动指令
Vue.use(vNumberScroll);
使用:
<span class="num1"v-number-scroll="{target: 1000, // 初始目标值duration: 1500, // 动画时长decimalPlaces: 2, // 0:整数,2:保留两位小数}"></span>