<template> 
 | 
  <view 
 | 
    class="tn-count-num-class tn-count-num" 
 | 
    :class="[fontColorClass]" 
 | 
    :style="{ 
 | 
      fontSize: fontSizeStyle || '50rpx', 
 | 
      fontWeight: bold ? 'bold' : 'normal', 
 | 
      color: fontColorStyle || '#080808' 
 | 
    }" 
 | 
  > 
 | 
    {{ displayValue }} 
 | 
  </view> 
 | 
</template> 
 | 
  
 | 
<script> 
 | 
  import componentsColorMixin from '../../libs/mixin/components_color.js' 
 | 
  export default { 
 | 
    name: 'tn-count-to', 
 | 
    mixins: [componentsColorMixin], 
 | 
    props: { 
 | 
      // 开始的数值,默认为0 
 | 
      startVal: { 
 | 
        type: Number, 
 | 
        default: 0 
 | 
      }, 
 | 
      // 结束目标数值 
 | 
      endVal: { 
 | 
        type: Number, 
 | 
        default: 0, 
 | 
        required: true 
 | 
      }, 
 | 
      // 是否自动开始 
 | 
      autoplay: { 
 | 
        type: Boolean, 
 | 
        default: true 
 | 
      }, 
 | 
      // 滚动到目标值的持续时间,单位为毫秒 
 | 
      duration: { 
 | 
        type: Number, 
 | 
        default: 2000 
 | 
      }, 
 | 
      // 是否在即将结束的时候使用缓慢滚动的效果 
 | 
      useEasing: { 
 | 
        type: Boolean, 
 | 
        default: true 
 | 
      }, 
 | 
      // 显示的小数位数 
 | 
      decimals: { 
 | 
        type: Number, 
 | 
        default: 0 
 | 
      }, 
 | 
      // 十进制的分割符 
 | 
      decimalSeparator: { 
 | 
        type: String, 
 | 
        default: '.' 
 | 
      }, 
 | 
      // 千分位的分隔符 
 | 
      // 类似金额的分割(¥23,321.05中的",") 
 | 
      thousandthsSeparator: { 
 | 
        type: String, 
 | 
        default: '' 
 | 
      }, 
 | 
      // 是否显示加粗字体 
 | 
      bold: { 
 | 
        type: Boolean, 
 | 
        default: false 
 | 
      } 
 | 
    }, 
 | 
    computed: { 
 | 
      countDown() { 
 | 
        return this.startVal > this.endVal 
 | 
      } 
 | 
    }, 
 | 
    data() { 
 | 
      return { 
 | 
        localStartVal: this.startVal, 
 | 
        localDuration: this.duration, 
 | 
        // 显示的数值 
 | 
        displayValue: this.formatNumber(this.startVal), 
 | 
        // 打印的数值 
 | 
        printValue: null, 
 | 
        // 是否暂停 
 | 
        paused: false, 
 | 
        // 开始时间戳 
 | 
        startTime: null, 
 | 
        // 停留时间戳 
 | 
        remainingTime: null, 
 | 
        // 当前时间戳 
 | 
        timestamp: null, 
 | 
        // 上一次的时间戳 
 | 
        lastTime: 0, 
 | 
        rAF: null 
 | 
      } 
 | 
    }, 
 | 
    watch: { 
 | 
      startVal() { 
 | 
        this.autoplay && this.start() 
 | 
      }, 
 | 
      endVal() { 
 | 
        this.autoplay && this.start() 
 | 
      } 
 | 
    }, 
 | 
    mounted() { 
 | 
      this.autoplay && this.start() 
 | 
    }, 
 | 
    methods: { 
 | 
      // 开始滚动 
 | 
      start() { 
 | 
        this.localStartVal = this.startVal 
 | 
        this.startTime = null 
 | 
        this.localDuration = this.duration 
 | 
        this.paused = false 
 | 
        this.rAF = this.requestAnimationFrame(this.count) 
 | 
      }, 
 | 
      // 重新开始 
 | 
      reStart() { 
 | 
        if (this.paused) { 
 | 
          this.resume() 
 | 
          this.paused = false 
 | 
        } else { 
 | 
          this.stop() 
 | 
          this.paused = true 
 | 
        } 
 | 
      }, 
 | 
      // 停止 
 | 
      stop() { 
 | 
        this.cancelAnimationFrame(this.rAF) 
 | 
      }, 
 | 
      // 恢复 
 | 
      resume() { 
 | 
        this.startTime = null 
 | 
        this.localDuration = this.remainingTime 
 | 
        this.localStartVal = this.printValue 
 | 
        this.requestAnimationFrame(this.count) 
 | 
      }, 
 | 
      // 重置 
 | 
      reset() { 
 | 
        this.startTime = null 
 | 
        this.cnacelAnimationFrame(this.rAF) 
 | 
        this.displayValue = this.formatNumber(this.startVal) 
 | 
      }, 
 | 
      // 销毁组件 
 | 
      destroyed() { 
 | 
        this.cancelAnimationFrame(this.rAF) 
 | 
      }, 
 | 
      // 累加时间 
 | 
      count(timestamp) { 
 | 
        if (!this.startTime) this.startTime = timestamp 
 | 
        this.timestamp = timestamp 
 | 
        const progress = timestamp - this.startTime 
 | 
        this.remainingTime = this.localDuration - progress 
 | 
        if (this.useEasing) { 
 | 
          if (this.countDown) { 
 | 
            this.printValue = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration) 
 | 
          } { 
 | 
            this.printValue = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration) 
 | 
          } 
 | 
        } else { 
 | 
          if (this.countDown) { 
 | 
            this.printValue = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration) 
 | 
          } else { 
 | 
            this.printValue = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration) 
 | 
          } 
 | 
        } 
 | 
        if (this.countDown) { 
 | 
          this.printValue = this.printValue < this.endVal ? this.endVal : this.printValue 
 | 
        } else { 
 | 
          this.printValue = this.printValue > this.endVal ? this.endVal : this.printValue 
 | 
        } 
 | 
         
 | 
        this.displayValue = this.formatNumber(this.printValue) 
 | 
        if (progress < this.localDuration) { 
 | 
          this.rAF = this.requestAnimationFrame(this.count) 
 | 
        } else { 
 | 
          this.$emit('end') 
 | 
        } 
 | 
      }, 
 | 
      // 缓动时间计算 
 | 
      easingFn(t, b, c, d) { 
 | 
        return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b 
 | 
      }, 
 | 
      // 请求帧动画 
 | 
      requestAnimationFrame(cb) { 
 | 
        const currentTime = new Date().getTime() 
 | 
        // 为了使setTimteout的尽可能的接近每秒60帧的效果 
 | 
        const timeToCall = Math.max(0, 16 - (currentTime - this.lastTime)) 
 | 
        const timerId = setTimeout(() => { 
 | 
          cb && cb(currentTime + timeToCall) 
 | 
        }, timeToCall) 
 | 
        this.lastTime = currentTime + timeToCall 
 | 
        return timerId 
 | 
      }, 
 | 
      // 清除帧动画 
 | 
      clearAnimationFrame(timerId) { 
 | 
        clearTimeout(timerId) 
 | 
      }, 
 | 
      // 格式化数值 
 | 
      formatNumber(number) { 
 | 
        const reg = /(\d+)(\d{3})/ 
 | 
        number = Number(number) 
 | 
        number = number.toFixed(Number(this.decimals)) 
 | 
        number += '' 
 | 
        const numberArray = number.split('.') 
 | 
        let num1 = numberArray[0] 
 | 
        const num2 = numberArray.length > 1 ? this.decimalSeparator + numberArray[1] : '' 
 | 
         
 | 
        if (this.thousandthsSeparator && !this.isNumber(this.thousandthsSeparator)) { 
 | 
          while(reg.test(num1)) { 
 | 
            num1 = num1.replace(reg, '$1' + this.thousandthsSeparator + '$2') 
 | 
          } 
 | 
        } 
 | 
        return num1 + num2 
 | 
      }, 
 | 
      // 判断是否为数字 
 | 
      isNumber(val) { 
 | 
        return !isNaN(parseFloat(val)) 
 | 
      } 
 | 
    } 
 | 
  } 
 | 
</script> 
 | 
  
 | 
<style lang="scss" scoped> 
 | 
   
 | 
  .tn-count-num { 
 | 
    /* #ifndef APP-NVUE */ 
 | 
    display: inline-flex; 
 | 
    /* #endif */ 
 | 
    text-align: center; 
 | 
    line-height: 1; 
 | 
  } 
 | 
</style> 
 |