炫酷的圓環載入及數字滾動效果(上)

三隻萌新發表於2018-12-21

炫酷的圓環載入及數字滾動效果(上)
實際專案開發時候需要實現圓環載入及數字滾動的效果,接下來分享下自己的實現思路和做法。

數字的滾動的實現思路

vue本身就是根據資料來驅動view層的顯示,實現數字的滾動本質就是設定一個延遲函式改變資料的同時,view層的顯示也會隨著改變達到漸變的效果。

元件化

為了考慮多種使用場景,將滾動抽離成元件,需要用到的屬性

引數 說明 型別 預設值
tag 標籤名 String 'span'
start 是否開始 Boolean true
startVal 起始值 Number / String 0
endVal 結束值 Number /String -
decimals 幾位小數 Number 2
duration 過渡時間 Number 2 (s)
isRestart 是否可以暫停 Boolean false

所以我們props的型別校驗如下

// 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函式。

過渡

init函式中最重要的就是設定了過渡的開始時間,計算出起始值到結束值總的路程。接下來就是數字滾動的過渡過程。

 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,200

  // 列印值
  printValue(value) {
    this.label.innerHTML = value.toFixed(this.decimals)
  }
複製程式碼

至此我們已經完成了數字滾動過渡功能,來看看製作的效果吧。

炫酷的圓環載入及數字滾動效果(上)
以為大功告成了,結果發現在我們更新結束值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函式後再來看過渡效果。

炫酷的圓環載入及數字滾動效果(上)
當然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的效果。下節將分享圓環載入的過渡效果

相關文章