vue2 实现数字滚动特效

首页统计栏数字实现数字滚动效果,废话不多说直接上代码:

javascript 复制代码
// 新增:执行数字滚动动画
    runCountAnimation(targetVal) {
      // 计算步长(目标值越大,步长越大,保证动画速度均匀)
      const step = Math.max(1, Math.ceil(targetVal / 100));
      // 设置定时器,实现平滑递增
      this.countTimer = setInterval(() => {
        // 判断:当前数字未达到目标值,继续递增
        if (this.currentCount < targetVal) {
          // 防止最后一步超出目标值,取最小值
          this.currentCount = Math.min(this.currentCount + step, targetVal);
        } else {
          // 达到目标值,清理定时器
          this.clearCountTimer();
        }
      }, 16); // 约60帧/秒,保证动画流畅
    },
    // 新增:清理定时器
    clearCountTimer() {
      if (this.countTimer) {
        clearInterval(this.countTimer);
        this.countTimer = null;
      }
    }

我这个是一个封装的组件,所以上面的执行方法逻辑如下:

javascript 复制代码
 watch: {
    // 监听目标count变化,触发数字滚动动画
    count(newVal, oldVal) {
      // 先清理原有定时器,防止重复触发
      this.clearCountTimer();
      // 重置当前显示数字为0
      this.currentCount = 0;
      // 若新值为0,直接结束,不执行动画
      if (newVal === 0) return;
      // 执行数字滚动动画
      this.runCountAnimation(newVal);
    }
  },
  mounted() {
    // 组件初始化时,触发一次动画(针对初始传入的count值)
    if (this.count !== 0) {
      this.runCountAnimation(this.count);
    }
  },
  beforeDestroy() {
    // 组件销毁前,清理定时器,防止内存泄露
    this.clearCountTimer();
  },

组件的完整代码,这个是jeecg boot 3.2.0 的前端项目,该兴趣可以看看,里面使用了j-ellispsis进行数字缩略

javascript 复制代码
<template>
  <a-card :loading="loading" :bordered="false">
    <div class="card">
      <div class="logo-c" :style="{background:bgColor}">
        <img :src="src" class="card-l" />
      </div>
      <div class="money-content">
        <div class="title"><j-ellipsis :value="title" :length="8"/></div>
        <div style="white-space: nowrap;">
          <!-- 保持原有格式化和省略逻辑,仅替换数据源为currentCount -->
          <span class="count" :style="{color:fontColor}">
            <j-ellipsis :value="String(formattedCount)" :length="8"/>
          </span>
          <span class="c-font" style="margin-left: 3px">{{formattedUnit}}</span>
        </div>
      </div>
    </div>
  </a-card>
</template>

<script>
export default {
  name: "MoneyCode",
  props: {
    title: {
      type: String,
      default: ''
    },
    total: {
      type: String,
      default: ''
    },
    loading: {
      type: Boolean,
      default: false
    },
    //字体颜色
    fontColor:{
      type: String,
      default: "#FF4D4F"
    },
    //背景颜色
    bgColor:{
      type: String,
      default: "#FFF1F0"
    },
    //数量(目标值)
    count:{
      type: Number,
      default: 0
    },
    //单位
    unit:{
      type: String,
      default: '笔'
    },
    src:{
      default: require("@/assets/day-money-count.svg")
    }
  },
  data() {
    return {
      currentCount: 0, // 新增:当前动画显示的数字(响应式)
      countTimer: null // 新增:定时器实例,用于清理定时器
    }
  },
  computed: {
    // 原有逻辑不变,仅将依赖的this.count改为this.currentCount
    formattedCount() {
      if (this.currentCount >= 10000) {
        return (this.currentCount / 10000).toFixed(3);
      }
      return this.currentCount;
    },
    formattedUnit() {
      // 注意:此处判断目标值count,而非当前动画值currentCount,保证单位格式化准确
      if (this.count >= 10000) {
        let locale = localStorage.getItem('locale');
        if(locale === 'en_GB'){
          return 'W ' + this.unit;
        }else if(locale === 'vi_VN'){
          return 'triệu ' + this.unit;
        } else{
          return '万' + this.unit;
        }
      }
      return this.unit;
    }
  },
  watch: {
    // 监听目标count变化,触发数字滚动动画
    count(newVal, oldVal) {
      // 先清理原有定时器,防止重复触发
      this.clearCountTimer();
      // 重置当前显示数字为0
      this.currentCount = 0;
      // 若新值为0,直接结束,不执行动画
      if (newVal === 0) return;
      // 执行数字滚动动画
      this.runCountAnimation(newVal);
    }
  },
  mounted() {
    // 组件初始化时,触发一次动画(针对初始传入的count值)
    if (this.count !== 0) {
      this.runCountAnimation(this.count);
    }
  },
  beforeDestroy() {
    // 组件销毁前,清理定时器,防止内存泄露
    this.clearCountTimer();
  },
  methods: {
    // 新增:执行数字滚动动画
    runCountAnimation(targetVal) {
      // 计算步长(目标值越大,步长越大,保证动画速度均匀)
      const step = Math.max(1, Math.ceil(targetVal / 100));
      // 设置定时器,实现平滑递增
      this.countTimer = setInterval(() => {
        // 判断:当前数字未达到目标值,继续递增
        if (this.currentCount < targetVal) {
          // 防止最后一步超出目标值,取最小值
          this.currentCount = Math.min(this.currentCount + step, targetVal);
        } else {
          // 达到目标值,清理定时器
          this.clearCountTimer();
        }
      }, 16); // 约60帧/秒,保证动画流畅
    },
    // 新增:清理定时器
    clearCountTimer() {
      if (this.countTimer) {
        clearInterval(this.countTimer);
        this.countTimer = null;
      }
    }
  }
}
</script>

使用组件,里面使用了vue-i18n进行国际化,感兴趣的可以了解我之前的文章
https://blog.csdn.net/a_biggerfish/article/details/147423588?spm=1001.2014.3001.5502

javascript 复制代码
 <a-row :gutter="[24,16]">
      <a-col :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.dailyPaymentFrequency')" :unit="$t('dashboard.count')" :count="baseInfo.dayPaymentsCount?baseInfo.dayPaymentsCount:0" :src="require('@/assets/day-money-count.svg')"></money-card>
      </a-col>
      <a-col v-has="'chart:all'" :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.dailyPaymentAmount')" :unit="$t('dashboard.RMB')" :count="baseInfo.dayPaymentsMoney?baseInfo.dayPaymentsMoney:0" :src="require('@/assets/day-money-count.svg')"></money-card>
      </a-col>
      <a-col v-has="'chart:all'"  :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.dailyAccumulatedUsage')" unit="m³" :count="baseInfo.dayWaterVolume?baseInfo.dayWaterVolume:0" font-color="#FFA940" bg-color="#FFF7E6" :src="require('@/assets/day-money.svg')"></money-card>
      </a-col>
      <a-col :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.dailyAccumulatedPayment')" :unit="$t('dashboard.RMB')" :count="baseInfo.dayPaymentsAmount?baseInfo.dayPaymentsAmount:0" font-color="#FFA940" bg-color="#FFF7E6" :src="require('@/assets/day-money.svg')"></money-card>
      </a-col>
      <a-col :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.monthlyPaymentFrequency')" :unit="$t('dashboard.count')" :count="baseInfo.monthPaymentsCount?baseInfo.monthPaymentsCount:0" font-color="#BAE637" bg-color="#F6FFED" :src="require('@/assets/month-money-count.svg')"></money-card>
      </a-col>
      <a-col v-has="'chart:all'"  :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.monthlyPaymentAmount')" :unit="$t('dashboard.RMB')" :count="baseInfo.monthPaymentsMoney?baseInfo.monthPaymentsMoney:0" font-color="#BAE637" bg-color="#F6FFED" :src="require('@/assets/month-money-count.svg')"></money-card>
      </a-col>
      <a-col v-has="'chart:all'"  :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.monthlyAccumulatedUsage')" unit="m³" :count="baseInfo.monthWaterVolume?baseInfo.monthWaterVolume:0" font-color="#FADB14" bg-color="#FEFFE6" :src="require('@/assets/month-money.svg')"></money-card>
      </a-col>
      <a-col :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.monthlyAccumulatedPayment')" :unit="$t('dashboard.RMB')" :count="baseInfo.monthPaymentsAmount?baseInfo.monthPaymentsAmount:0" font-color="#FADB14" bg-color="#FEFFE6" :src="require('@/assets/month-money.svg')"></money-card>
      </a-col>
      <a-col :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.monthlyAccountOpeningsNumber')" :unit="$t('dashboard.households')" bg-color="#E6F7FF" font-color="#40A9FF" :count="baseInfo.monthOpenAccountCount?baseInfo.monthOpenAccountCount:0" :src="require('@/assets/month-home.svg')"></money-card>
      </a-col>
      <a-col :sm="24" :md="12" :xl="6" :xxl="4">
        <money-card :title="$t('dashboard.totalAccountOpeningsNumber')" :unit="$t('dashboard.households')" :count="baseInfo.totalOpenAccountCount?baseInfo.totalOpenAccountCount:0" font-color="#9254DE" bg-color="#F9F0FF" :src="require('@/assets/all-home.svg')"></money-card>
      </a-col>
    </a-row>

效果如下:

https://live.csdn.net/v/508969

20260106_165126

相关推荐
NiceCloud喜云5 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
wordbaby6 小时前
React Native + RNOH:跨页面数据回传的最佳实践与避坑指南
前端·react native
丷丩6 小时前
MapLibre GL JS第22课:查看本地GeoJSON
前端·javascript·map·mapbox·maplibre gl js
Front思7 小时前
AI前端工程师需要具备能力+
前端·人工智能·ai
ZC跨境爬虫9 小时前
跟着 MDN 学CSS day_29:(掌握文本与字体样式的核心艺术)
前端·css·ui·html·tensorflow
李子琪。9 小时前
网络空间安全深度实战:CSRF 漏洞原理剖析与基于 Token 的纵深防御体系构建(全栈实验报告)
前端·安全·csrf
冰暮流星10 小时前
javascript之history对象介绍
前端·笔记
IT_陈寒10 小时前
Vite热更新失灵?你可能漏了这个配置
前端·人工智能·后端
丷丩10 小时前
MapLibre GL JS第19课:实时更新要素
前端·javascript·gis·map·mapbox·maplibre gl js
Mr.Daozhi10 小时前
RAG 进阶实战:跑通 Demo 后我连续翻了 6 次车,逐一修复才真正可用(含 Gradio Web 版)
前端·数据库·langchain·大模型·gradio·rag·科研工具