Vue3+TS實(shí)現(xiàn)數(shù)字滾動(dòng)效果CountTo組件
前言
最近開(kāi)發(fā)有個(gè)需求需要酷炫的文字滾動(dòng)效果,發(fā)現(xiàn)vue2版本的CountTo組件不適用與Vue3,沒(méi)有輪子咋辦,那咱造一個(gè)唄。其實(shí)大多數(shù)版本更替導(dǎo)致公共組件不可用,最簡(jiǎn)單的做法就是在原版本的基礎(chǔ)上進(jìn)行修改調(diào)整,總體來(lái)講花費(fèi)的時(shí)間成本以及精力成本最低。
思考

先看下效果,明確需求,然后開(kāi)始搬磚。
明確基礎(chǔ)功能
- 有開(kāi)始值、結(jié)束值以及動(dòng)畫(huà)持續(xù)時(shí)間
- 默認(rèn)分隔符、自動(dòng)播放
擴(kuò)展功能
- 自動(dòng)播放可配置
- 分隔符可自定義
- 前、后綴
- 動(dòng)畫(huà)配置項(xiàng)
實(shí)踐
定義參數(shù)
const props = {
start: {
type: Number,
required: false,
default: 0
},
end: {
type: Number,
required: false,
default: 0
},
duration: {
type: Number,
required: false,
default: 5000
},
autoPlay: {
type: Boolean,
required: false,
default: true
},
decimals: {
type: Number,
required: false,
default: 0,
validator(value) {
return value >= 0
}
},
decimal: {
type: String,
required: false,
default: '.'
},
separator: {
type: String,
required: false,
default: ','
},
prefix: {
type: String,
required: false,
default: ''
},
suffix: {
type: String,
required: false,
default: ''
},
useEasing: {
type: Boolean,
required: false,
default: true
},
easingFn: {
type: Function,
default(t, b, c, d) {
return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b
}
}
}定義一個(gè)開(kāi)始函數(shù)
// 定義一個(gè)計(jì)算屬性,當(dāng)開(kāi)始數(shù)字大于結(jié)束數(shù)字時(shí)返回true
const stopCount = computed(() => {
return props.start > props.end
})
const startCount = () => {
state.localStart = props.start
state.startTime = null
state.localDuration = props.duration
state.paused = false
state.rAF = requestAnimationFrame(count)
}
watch(() => props.start, () => {
if (props.autoPlay) {
startCount()
}
})
watch(() => props.end, () => {
if (props.autoPlay) {
startCount()
}
})
// dom掛在完成后執(zhí)行一些操作
onMounted(() => {
if (props.autoPlay) {
startCount()
}
emit('onMountedcallback')
})
// 組件銷(xiāo)毀時(shí)取消動(dòng)畫(huà)
onUnmounted(() => {
cancelAnimationFrame(state.rAF)
})核心方法
const count = (timestamp) => {
if (!state.startTime) state.startTime = timestamp
state.timestamp = timestamp
const progress = timestamp - state.startTime
state.remaining = state.localDuration - progress
// 是否使用速度變化曲線(xiàn)
if (props.useEasing) {
if (stopCount.value) {
state.printVal = state.localStart - props.easingFn(progress, 0, state.localStart - props.end, state.localDuration)
} else {
state.printVal = props.easingFn(progress, state.localStart, props.end - state.localStart, state.localDuration)
}
} else {
if (stopCount.value) {
state.printVal = state.localStart - ((state.localStart - props.end) * (progress / state.localDuration))
} else {
state.printVal = state.localStart + (props.end - state.localStart) * (progress / state.localDuration)
}
}
if (stopCount.value) {
state.printVal = state.printVal < props.end ? props.end : state.printVal
} else {
state.printVal = state.printVal > props.end ? props.end : state.printVal
}
state.displayValue = formatNumber(state.printVal)
if (progress < state.localDuration) {
state.rAF = requestAnimationFrame(count)
} else {
emit('callback')
}
}配置項(xiàng)
| 屬性 | 描述 | 類(lèi)型 | 默認(rèn)值 |
|---|---|---|---|
| startVal | 開(kāi)始值 | Number | 0 |
| endVal | 結(jié)束值 | Number | 0 |
| duration | 持續(xù)時(shí)間 | Number | 0 |
| autoplay | 自動(dòng)播放 | Boolean | true |
| decimals | 要顯示的小數(shù)位數(shù) | Number | 0 |
| decimal | 十進(jìn)制分割 | String | , |
| separator | 分隔符 | String | , |
| prefix | 前綴 | String | '' |
| suffix | 后綴 | String | '' |
| useEasing | 使用緩和功能 | Boolean | true |
| easingFn | 緩和回調(diào) | Function | - |
注:當(dāng)autoplay:true時(shí),它將在startVal或endVal更改時(shí)自動(dòng)啟動(dòng)
功能
| 函數(shù)名 | 描述 |
|---|---|
| mountedCallback | 掛載以后返回回調(diào) |
| start | 開(kāi)始計(jì)數(shù) |
| pause | 暫停計(jì)數(shù) |
| reset | 重置countTo |
組件
組件同步在git組件庫(kù)了https://github.com/kinoaa/kinoaa-components/tree/main/countTo
import {
defineComponent, reactive, computed, onMounted, watch, onUnmounted
} from 'vue'
const props = {
start: {
type: Number,
required: false,
default: 0
},
end: {
type: Number,
required: false,
default: 2022
},
duration: {
type: Number,
required: false,
default: 5000
},
autoPlay: {
type: Boolean,
required: false,
default: true
},
decimals: {
type: Number,
required: false,
default: 0,
validator(value) {
return value >= 0
}
},
decimal: {
type: String,
required: false,
default: '.'
},
separator: {
type: String,
required: false,
default: ','
},
prefix: {
type: String,
required: false,
default: ''
},
suffix: {
type: String,
required: false,
default: ''
},
useEasing: {
type: Boolean,
required: false,
default: true
},
easingFn: {
type: Function,
default(t, b, c, d) {
return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b
}
}
}
export default defineComponent({
name: 'CountTo',
props: props,
emits: ['onMountedcallback', 'callback'],
setup(props, {emit}) {
const isNumber = (val) => {
return !isNaN(parseFloat(val))
}
// 格式化數(shù)據(jù),返回想要展示的數(shù)據(jù)格式
const formatNumber = (val) => {
val = val.toFixed(props.start)
val += ''
const x = val.split('.')
let x1 = x[0]
const x2 = x.length > 1 ? props.decimal + x[1] : ''
const rgx = /(\d+)(\d{3})/
if (props.separator && !isNumber(props.separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + props.separator + '$2')
}
}
return props.prefix + x1 + x2 + props.suffix
}
const state = reactive<{
localStart: number
displayValue: number|string
printVal: any
paused: boolean
localDuration: any
startTime: any
timestamp: any
remaining: any
rAF: any
}>({
localStart: props.start,
displayValue: formatNumber(props.start),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null
})
// 定義一個(gè)計(jì)算屬性,當(dāng)開(kāi)始數(shù)字大于結(jié)束數(shù)字時(shí)返回true
const stopCount = computed(() => {
return props.start > props.end
})
const startCount = () => {
state.localStart = props.start
state.startTime = null
state.localDuration = props.duration
state.paused = false
state.rAF = requestAnimationFrame(count)
}
watch(() => props.start, () => {
if (props.autoPlay) {
startCount()
}
})
watch(() => props.end, () => {
if (props.autoPlay) {
startCount()
}
})
// dom掛在完成后執(zhí)行一些操作
onMounted(() => {
if (props.autoPlay) {
startCount()
}
emit('onMountedcallback')
})
const count = (timestamp) => {
if (!state.startTime) state.startTime = timestamp
state.timestamp = timestamp
const progress = timestamp - state.startTime
state.remaining = state.localDuration - progress
// 是否使用速度變化曲線(xiàn)
if (props.useEasing) {
if (stopCount.value) {
state.printVal = state.localStart - props.easingFn(progress, 0, state.localStart - props.end, state.localDuration)
} else {
state.printVal = props.easingFn(progress, state.localStart, props.end - state.localStart, state.localDuration)
}
} else {
if (stopCount.value) {
state.printVal = state.localStart - ((state.localStart - props.end) * (progress / state.localDuration))
} else {
state.printVal = state.localStart + (props.end - state.localStart) * (progress / state.localDuration)
}
}
if (stopCount.value) {
state.printVal = state.printVal < props.end ? props.end : state.printVal
} else {
state.printVal = state.printVal > props.end ? props.end : state.printVal
}
state.displayValue = formatNumber(state.printVal)
if (progress < state.localDuration) {
state.rAF = requestAnimationFrame(count)
} else {
emit('callback')
}
}
// 組件銷(xiāo)毀時(shí)取消動(dòng)畫(huà)
onUnmounted(() => {
cancelAnimationFrame(state.rAF)
})
return () => (
state.displayValue
)
}
})到此這篇關(guān)于Vue3+TS實(shí)現(xiàn)數(shù)字滾動(dòng)效果CountTo組件的文章就介紹到這了,更多相關(guān)Vue3數(shù)字滾動(dòng)效果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue 項(xiàng)目打包時(shí)樣式及背景圖片路徑找不到的解決方式
今天小編就為大家分享一篇vue 項(xiàng)目打包時(shí)樣式及背景圖片路徑找不到的解決方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
基于Vue.js+Nuxt開(kāi)發(fā)自定義彈出層組件
這篇文章主要介紹了基于Vue.js+Nuxt開(kāi)發(fā)自定義彈出層組件,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
淺談Vue3.0新版API之composition-api入坑指南
這篇文章主要介紹了Vue3.0新版API之composition-api入坑指南,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
iView-admin 動(dòng)態(tài)路由問(wèn)題的解決方法
這篇文章主要介紹了iView-admin 動(dòng)態(tài)路由問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
vue2和elementUI?實(shí)現(xiàn)落日余暉登錄頁(yè)和滑塊校驗(yàn)功能
這篇文章主要介紹了vue2和elementUI打造落日余暉登錄頁(yè)和滑塊校驗(yàn),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
vue結(jié)合echarts繪制一個(gè)支持切換的折線(xiàn)圖實(shí)例
這篇文章主要介紹了vue結(jié)合echarts繪制一個(gè)支持切換的折線(xiàn)圖實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue移動(dòng)端使用canvas簽名的實(shí)現(xiàn)
這篇文章主要介紹了vue移動(dòng)端使用canvas簽名的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Vue?數(shù)據(jù)綁定事件綁定樣式綁定語(yǔ)法示例
這篇文章主要為大家介紹了Vue?數(shù)據(jù)綁定事件綁定樣式綁定語(yǔ)法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

