炫酷的圆环加载及数字滚动效果(上)

实际项目开发时候须要实现圆环加载及数字滚动的效果,接下来分享下本身的实现思路和作法。

数字的滚动的实现思路

vue自己就是根据数据来驱动view层的显示,实现数字的滚动本质就是设置一个延迟函数改变数据的同时,view层的显示也会随着改变达到渐变的效果。vue

组件化

为了考虑多种使用场景,将滚动抽离成组件,须要用到的属性安全

参数 说明 类型 默认值
tag 标签名 String 'span'
start 是否开始 Boolean true
startVal 起始值 Number / String 0
endVal 结束值 Number /String -
decimals 几位小数 Number 2
duration 过渡时间 Number 2 (s)
isRestart 是否能够暂停 Boolean false

因此咱们props的类型校验以下bash

// index.vue
<script>
import CountUp from './countup.js'
export default {
  name: 'countup',
  mounted() {
    this.$nextTick(() => {
      this._countup = new CountUp(
        this.$el,
        this.startVal,
        this.endVal,
        this.decimals,
        this.duration
      )
      if (this.start) {
        this._countup.init()
      }
    })
  },
  props: {
    tag: {
      type: String,
      default: 'span'
    },
    start: {
      type: Boolean,
      default: true
    },
    startVal: {
      type: Number | String,
      default: 0
    },
    endVal: {
      type: Number | String,
      required: true
    },
    decimals: {
      type: Number,
      default: 2
    },
    duration: {
      type: Number,
      default: 2
    },
    isRestart: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      times: 0
    }
  },
  methods: {
    onPauseResumeClick() {
      if (this.isRestart) {
        if (this.times === 0) {
          this._countup.pauseResume()
          this.times++
        } else {
          this._countup.restart()
          this.times = 0
        }
      }
    }
  },
  render(h) {
    return h(
      this.tag,
      {
        on: {
          click: this.onPauseResumeClick
        }
      },
      [this.startVal]
    )
  },
  watch: {
    start(val) {
      if (val) {
        this._countup.init()
      }
    },
    endVal(val) {
      this._countup.updateNew(this.endVal)
    }
  }
}
</script>
复制代码

逻辑部分抽离出来放在 countup.js文件中。首先来看看index.vue 文件,在mounted中实例化了一个CountUp类,而且向这个类中传递了咱们props接收到的参数。而且在初始化和start值发生改变的时候触发类中的init函数,在endVal改变的时候触发类的updateNew函数。最终经过render函数将值渲染在view层。分析完index.vue文件后,好奇到底countup.js定义了哪些函数,接下来看下数字过渡的实现。函数

CountUp类

首先咱们来看代码结构,暂时不关心细节作了什么,constructor构造函数中接收到外部传入的值,而且将这些值添加到实例对象上。这样类上的方法(也就是类的prototype原型上的方法)均可以经过this访问到实例的对象的值。组件化

class CountUp {
  constructor(target, startVal, endVal, decimals, duration) {
    this.target = target
    this.startVal = startVal
    this.endVal = endVal
    this.decimals = decimals
    this.duration = Number(this.duration) * 1000 || 2000
  }
  // 初始化
  init() {
    // 拿到DOM
    this.label =
      typeof this.target === 'string'
        ? document.getElementById(this.target)
        : this.target
    this.startVal = Number(this.startVal)
    this.endVal = Number(this.endVal)
    this.frameVal = this.startVal
    this.startTime = new Date()
    this.progress = this.endVal - this.frameVal
    this.update()
  }
  // 更新
  update() {
    this.rAF = setInterval(() => {
      const time = new Date() - this.startTime
      const speed =
        ((new Date() - this.startTime) / this.duration) * this.progress
      if (time >= this.duration) {
        clearInterval(this.rAF)
        this.frameVal = this.endVal
        this.startVal = this.frameVal
      } else {
        this.frameVal = this.startVal + speed
      }
      this.printValue(this.frameVal)
    })
  }
  // 打印值
  printValue(value) {
    this.label.innerHTML = value.toFixed(this.decimals)
  }
  // 有新的结束值
  updateNew(newEndVal) {
    this.pauseResume()
    this.endVal = newEndVal
    this.init()
  }
  // 暂停
  pauseResume() {
    clearInterval(this.rAF)
    this.startVal = this.frameVal
  }
  // 从新开始
  restart() {
    this.init()
  }
}
export default CountUp
复制代码

constructor构造函数中拿到数据,而后经过各个prototype上的方法如:printValue(打印值)、updateNew(更新)......实现代码逻辑。有了对这个类结构的认识,咱们来看看每一个模块都作了什么事。
在mounted钩子中咱们经过this._countup.init()初始化,在初始化过程当中主要作了一些安全转换,判断传入的$el若是未字符串则获取对应id的DOM,不然将target自己就是DOM,将起始值和结束值都转为数字类型,关键点开启计时设置startTime,咱们后面会经过时间来判断是否已经达到目标值用来判断是否中止过渡,计算出总的路程的绝对值。在初始化的结束时开启执行下一个execute函数。ui

过渡

init函数中最重要的就是设置了过渡的开始时间,计算出起始值到结束值总的路程。接下来就是数字滚动的过渡过程。this

update() {
    this.rAF = setInterval(() => {
      const time = new Date() - this.startTime
      const speed =
        ((new Date() - this.startTime) / this.duration) * this.progress
      if (time >= this.duration) {
        clearInterval(this.rAF)
        this.frameVal = this.endVal
        this.startVal = this.frameVal
      } else {
        this.frameVal = this.startVal + speed
      }
      this.printValue(this.frameVal)
    })
  }
复制代码

update更新函数中咱们设置一个setInterval重复执行数字的累计过程,经过单位时间/总时间*路程=速度的公式来累计,要注意的是speed自己是有正负的因此不须要考虑是加仍是减的问题。而且咱们经过printValue函数将每次更新的值更新到DOM节点上。而且在这个函数中控制DOM的显示,如 toFixed来控制数字显示的小数点位数,固然也能够控制整数部分每三位加一个,的显示如:10,200spa

// 打印值
  printValue(value) {
    this.label.innerHTML = value.toFixed(this.decimals)
  }
复制代码

至此咱们已经完成了数字滚动过渡功能,来看看制做的效果吧。prototype

觉得大功告成了,结果发如今咱们更新结束值5000在未达到时又更改成500会瞬间改回来。

// 有新的结束值
  updateNew(newEndVal) {
    this.pauseResume()
    this.endVal = newEndVal
    this.init()
  }
    // 暂停
  pauseResume() {
    clearInterval(this.rAF)
    this.startVal = this.frameVal
  }
复制代码

咱们须要在更新endVal以前将上一个的定时器清除掉,不然会一直使用通一个setInterval 。因此在500 -> 5000 的中途咱们将值改成500至关于startVal和endVal都是500天然不会又过渡效果,而且会当即返回500的值。加上了pauseResume函数后再来看过渡效果。 rest

固然pauseResume函数咱们也能够设置为手动触发,咱们在methods中定义好暂定函数,而且判断props是否设置了 isRestart为true是否开启可暂停模式,在为真的状况下判断 点击次数times为0时暂停,为1时从新开始滚动。

methods: {
    onPauseResumeClick() {
      if (this.isRestart) {
        if (this.times === 0) {
          this._countup.pauseResume()
          this.times++
        } else {
          this._countup.restart()
          this.times = 0
        }
      }
    }
  },
 render(h) {
    return h(
      this.tag,
      {
        on: {
          click: this.onPauseResumeClick
        }
      },
      [this.startVal]
    )
  },
复制代码

巧用 v-if v-show 完成卡到千卡单位的转换

// number1
  <span v-if="isComplate">
    <count :start-val="1"
      :end-val="formatConsume"></count>千卡
  </span>
// number2
  <span v-show="!isComplate">
    <count :start-val="0"
      :end-val="1000"></count>卡
  </span>
复制代码

经过v-if从新渲染和v-show显示隐藏的机制,isComplate是用来判断是否已经达到1000,这里用v-if来控制number1来从新渲染,若是这里用v-show则页面进入的时候就会开始加载过渡效果。不是咱们想要的效果。之因此number2要用v-show是将其隐藏掉,若是是v-if直接消失在DOM会再触发transition的过渡效果,过渡将变成500->5000->500的效果,咱们只须要将其隐藏掉同时显示number1的过渡效果便可。

结语

咱们完成了数字过渡的组件,首先经过index.vue的prop接受参数,将逻辑部分放在countup.js中经过引入后实例化这个类。在初始化和更新值的时候调用类中的方法达到更新DOM的效果。下节将分享圆环加载的过渡效果

相关文章
相关标签/搜索