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定義了哪些函數,接下來看下數字過渡的實現。函數
首先咱們來看代碼結構,暫時不關心細節作了什麼,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,200
spa
// 打印值
printValue(value) {
this.label.innerHTML = value.toFixed(this.decimals)
}
複製代碼
至此咱們已經完成了數字滾動過渡功能,來看看製做的效果吧。prototype
// 有新的結束值
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
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]
)
},
複製代碼
// 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的效果。下節將分享圓環加載的過渡效果
。