封装数字滚动组件

151 阅读2分钟

对于数字滚动特效在一些大屏项目里是非常受欢迎的

image.png

写在前面

参考:海的对岸
链接:juejin.cn/post/702141…

参考:你可能需要这样的大屏数字滚动效果 链接:juejin.cn/post/684490…

实现思路

  1. 用户传入需要进行滚动的数字(数值)
  2. 当组件在页面渲染出来后变会从0依次递增到该数值
  3. 用户可以传入time即时间去控制从0递增到该值的时长
  4. 用户也可以传入isThousandthPercentile来实现数值是否需要以千分位形式进行显示

第二步拆开来,用代码来表达,就是“

  • a. 设置一个步进值(增量)step,

  • b. 整个计时器time,设置一个开始值startstart 和 传过来的具体值(value)比较,startvalue小,继续使用计时器time,给start加上增量step,再比较startvalue的大小,比到startvalue大的时候,结束

具体代码

countToNumber.vue

<template>
  <div class="number-grow-warp">
    <span
      ref="numberGrow"
      :data-time="time"
      class="number-grow"
      :data-value="value"
      >0</span
    >
  </div>
</template>
<script>
export default {
  props: {
    value: {
      type: Number,
      default() {
        return 720;
      },
    },
    time: {
      type: Number,
      default() {
        return 1;
      },
    },
    isThousandthPercentile: {
      type: Boolean,
      default() {
        return true;
      },
    },
  },
  data() {
    return {};
  },
  mounted() {
    this.numberGrow(this.$refs.numberGrow);
  },
  watch: {
    value: {
      immediate: true,
      handler(newVal, oldVal) {
        this.$nextTick(() => {
          this.numberGrow(this.$refs.numberGrow);
        });
      },
    },
  },
  methods: {
    // 文字变化效果
    numberGrow(ele) {
      //【调速度 1 ,步进值, 即每次跳的时候,增加的一个增量】
      let step = parseInt((this.value * 100) / (this.time * 1000));
      // 设置当前值:计时器持续运行时,每次页面上显示的跳动值
      let current = 0;
      // 设置开始值
      let start = 0;
      // 设置定时器,用来反复横跳的
      let t = setInterval(() => {
        // 每次增加一点步进值
        start += step;
        // 开始值大于传过来的的值,说明 到点了,不用 继续横跳了
        if (start > this.value) {
          clearInterval(t);
          // 把穿过的值赋给start,结束
          start = this.value;
          // 清掉计时器
          t = null;
        }
        if (start == 0) {
          start = this.value;
          clearInterval(t);
        }
        // 当前值等于开始值,那就结束
        if (this.value === 0) {
          return;
        }
        current = start;

        // 正则
        if (this.isThousandthPercentile) {
          ele.innerHTML = current
            .toString()
            .replace(/(\d)(?=(?:\d{3}[+]?)+$)/g, "$1,");
        } else {
          ele.innerHTML = current.toString();
        }
      }, this.time * 100); // 【这里调速度 2, 通俗地讲,这里是页面上,肉眼能看到的跳动频率】
    },
  },
};
</script>
<style lang="scss" scoped>
.number-grow-warp {
  transform: translateZ(0);
}
.number-grow {
  display: block;
}
</style>

使用方式:

<div class="item-name">
  <countToNumber
    :value="item.VALUE"
    :time="2"
    :isThousandthPercentile="false"
  />
  <span class="item-unit">/{{ item.UNIT }}</span>
</div>

export default {
  components: {
    countToNumber,
  },
}

image.png

实现方式二

<template>
  <div class="chartNum">
     <h3 class="orderTitle">XX模块展示:</h3>
     <div class="box-item">
      <li :class="{'number-item': !isNaN(item), 'mark-item': isNaN(item) }"
       v-for="(item,index) in orderNum"
       :key="index">
        <span v-if="!isNaN(item)">
         <i ref="numberItem" :myIndex="index" :myValue="item">0123456789</i>
        </span>
       <span class="comma" v-else>{{item}}</span>
      </li>
     </div>
    </div>
 </template>
 <script>
  export default {
    props: {
      value: {
        type: Number, // 具体数值
        default() {
          return 0;
        },
      },
      time: {
        type: Number, // 滚动要花的时间,单位秒
        default() {
          return 3;
        },
      },
    },
   data() {
    return {
     orderNum: ['0', '0', ',', '0', '0', '0', ',', '0', '0', '0'], // 默认订单总数
    }
   },
   mounted() {
    this.toOrderNum(this.value) // 这里输入数字即可调用
    this.increaseNumber(this.time);
   },
   methods: {
    // 定时增长数字
    increaseNumber (time) {
      let self = this
      this.timer = setInterval(() => {
      self.newNumber = self.newNumber + self.getRandomNumber(1, 100)
      self.setNumberTransform()
      }, time * 1000)
    },
     // 设置文字滚动
    setNumberTransform() {
     // 拿到数字的ref,计算元素数量 numberItems数组自身顺序有问题,需要处理成和数值保持一致
     const numberItems = this.$refs.numberItem
     numberItems.sort((a, b) => {
        // 如果value相同,按照value的降序
        if (a.attributes.myIndex.value === b.attributes.myIndex.value) {
          return b.attributes.myIndex.value - a.attributes.myIndex.value;
        }
        return a.attributes.myIndex.value - b.attributes.myIndex.value;
      });
      
      // 在 template 里面加上 myIndex 和 myValue 属性,是因为有次发现,显示出来的结果不准确
      // 经排查,发现是 numberItems字段里面获取的 html元素顺序不对,所以重新给他排好序
      // 注意:numberItems字段里面获取的 html元素顺序不对 是偶尔发生的现象,但是为了安全起见,还是处理下
      
     const numberArr = this.orderNum.filter(item => !isNaN(item))
     // 结合CSS 对数字字符进行滚动,显示订单数量
     for (let index = 0; index < numberItems.length; index++) {
     const elem = numberItems[index]
     elem.style.transform = `translate(-50%, -${numberArr[index] * 10}%)`
     }
    },
    getRandomNumber(min, max) {
      return Math.floor(Math.random() * (max - min + 1) + min)
    },
    // 处理传过来的具体值value
    toOrderNum(num) {
     num = num.toString()
     // 把具体值value变成字符串
     if (num.length < 8) {
     num = '0' + num // 如未满八位数,添加"0"补位
     this.toOrderNum(num) // 递归添加"0"补位
     } else if (num.length === 8) {
     // 具体值value中加入逗号
     num = num.slice(0, 2) + ',' + num.slice(2, 5) + ',' + num.slice(5, 8)
     this.orderNum = num.split('') // 将其便变成数据,渲染至滚动数组
     } else {
     // 具体值value数字超过八位显示异常
     this.$message.warning('xxx数量过大,显示异常,请联系后台管理员')
     }
    },
   }
  }
 </script>
 <style scoped lang='scss'>
   /*具体值value总量滚动数字设置*/
  .box-item {
   position: relative;
   height: 100px;
   font-size: 54px;
   line-height: 41px;
   text-align: center;
   list-style: none;
   color: #2D7CFF;
   writing-mode: vertical-lr;
   text-orientation: upright;
   /*文字禁止编辑*/
   -moz-user-select: none; /*火狐*/
   -webkit-user-select: none; /*webkit浏览器*/
   -ms-user-select: none; /*IE10*/
   -khtml-user-select: none; /*早期浏览器*/
   user-select: none;
   /* overflow: hidden; */
  }
  /* 默认逗号设置 */
  .mark-item {
   width: 10px;
   height: 100px;
   margin-right: 5px;
   line-height: 10px;
   font-size: 48px;
   position: relative;
   & > span {
    position: absolute;
    width: 100%;
    bottom: 0;
    writing-mode: vertical-rl;
    text-orientation: upright;
   }
  }
  /*滚动数字设置*/
  .number-item {
   width: 41px;
   height: 75px;
   background: #ccc;
   list-style: none;
   margin-right: 5px;
   background:rgba(250,250,250,1);
   border-radius:4px;
   border:1px solid rgba(221,221,221,1);
   & > span {
    position: relative;
    display: inline-block;
    margin-right: 10px;
    width: 100%;
    height: 100%;
    writing-mode: vertical-rl;
    text-orientation: upright;
    overflow: hidden;
    & > i {
     font-style: normal;
     position: absolute;
     top: 11px;
     left: 50%;
     transform: translate(-50%,0);
     transition: transform 1s ease-in-out;
     letter-spacing: 10px;
    }
   }
  }
  .number-item:last-child {
   margin-right: 0;
  }
 </style>