之前vue2使用vue-count-to很方便,直接安装使用。在vue3里面不支持vue-count-to插件,无法使用vue-count-to实现数字动效,数字自动分割,vue-count-to主要针对vue2使用,vue3按照会报错:
TypeError: Cannot read properties of undefined (reading '_c')
找了一遍没发现有TS版的,于是动手在原本基础上简单改了一下,主要是在CountTo组件和animationFrame上改动
CountTo改动如下:
<script lang="ts" name="index" setup>
import { onMounted, onUnmounted, reactive, watch, computed } from "vue"
import { requestAnimationFrame, cancelAnimationFrame } from './animationFrame'
// 组件参数
const props = withDefaults(defineProps<{
startVal?: number // 开始数字 默认 0
endVal?: number // 结束数字 默认 2022
duration?: number // 动画时间 默认 3000毫秒
autoPlay?: boolean // 自动播放 默认 true
decimals?: number // 保留小数位 默认不保留
decimal?: string // 小数点
separator?: string // 分隔符
prefix?: string // 前缀
suffix?: string // 后缀
useEasing?: boolean // 使用缓和动画
easingFn?: (t: number, b: number, c: number, d: number) => any // 缓和动画函数
}>(), {
startVal: 0,
endVal: 2022,
duration: 3000,
autoPlay: true,
decimals: 0,
decimal: '.',
separator: ',',
prefix: '',
suffix: '',
useEasing: true,
easingFn: (t: number, b: number, c: number, d: number) => (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
})
const isNumber = (val: string) => !isNaN(parseFloat(val))
// 格式化数据,返回想要展示的数据格式
const formatNumber = (num: number) => {
const params = `${num.toFixed(props.decimals)}`
const x = params.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: props.startVal,
displayValue: formatNumber(props.startVal),
printVal: 0,
paused: false,
localDuration: props.duration,
startTime: 0,
timestamp: 0,
remaining: 0,
rAF: null,
})
// 定义一个计算属性,当开始数字大于结束数字时返回true
const stopCount = computed((): boolean => props.startVal > props.endVal)
// 定义父组件的自定义事件,子组件以触发父组件的自定义事件
const emits = defineEmits(['onMountedcallback', 'callback'])
const start = () => {
state.localStart = props.startVal
state.startTime = 0
state.localDuration = props.duration
state.paused = false
state.rAF = requestAnimationFrame(count)
}
// 恢复计数
const resume = () => {
state.startTime = 0
state.localDuration = +state.remaining
state.localStart = +state.printVal
requestAnimationFrame(count)
}
// 暂停计数
const pause = () => {
cancelAnimationFrame(state.rAF)
}
// 暂停重新计数
const pauseResume = () => {
if (state.paused) {
resume()
state.paused = false
} else {
pause()
state.paused = true
}
}
// 重置
const reset = () => {
state.startTime = 0
cancelAnimationFrame(state.rAF)
state.displayValue = formatNumber(props.startVal)
}
const count = (timestamp: number) => {
if (!state.startTime) state.startTime = timestamp
state.timestamp = timestamp
const progress: number = timestamp - state.startTime
state.remaining = state.localDuration - progress
// 是否使用速度变化曲线
if (props.useEasing) {
if (stopCount.value) {
state.printVal =
state.localStart -
props.easingFn(
progress,
0,
state.localStart - props.endVal,
state.localDuration
);
} else {
state.printVal = props.easingFn(
progress,
state.localStart,
props.endVal - state.localStart,
state.localDuration
);
}
} else {
if (stopCount.value) {
state.printVal =
state.localStart -
(state.localStart - props.endVal) * (progress / state.localDuration);
} else {
state.printVal =
state.localStart +
(props.endVal - state.localStart) * (progress / state.localDuration);
}
}
if (stopCount.value) {
state.printVal = state.printVal < props.endVal ? props.endVal : state.printVal;
} else {
state.printVal = state.printVal > props.endVal ? props.endVal : state.printVal;
}
state.displayValue = formatNumber(state.printVal);
if (progress < state.localDuration) {
state.rAF = requestAnimationFrame(count);
} else {
emits("callback");
}
}
watch(
() => props.startVal,
() => {
if (props.autoPlay) start()
}
)
watch(
() => props.endVal,
() => {
if (props.autoPlay) start()
}
)
onMounted(() => {
if(props.autoPlay) start()
emits("onMountedcallback")
})
// 组件销毁时取消动画
onUnmounted(() => {
cancelAnimationFrame(state.rAF)
})
</script>
<template>
{{ state.displayValue }}
</template>
animationFrame改动如下:
let lastTime: number = 0
const prefixes: string[] = 'webkit moz ms o'.split(' ') // 各浏览器前缀
let requestAnimationFrame: any
let cancelAnimationFrame: any
// 判断是否是服务器环境
const isServer: boolean = typeof window === 'undefined'
if (isServer) {
requestAnimationFrame = function () {
return
}
cancelAnimationFrame = function () {
return
}
} else {
requestAnimationFrame = window.requestAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame
let prefix: string
// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) { break }
prefix = prefixes[i]
requestAnimationFrame = requestAnimationFrame || window[`${prefix}RequestAnimationFrame` as any]
cancelAnimationFrame = cancelAnimationFrame || window[`${prefix}CancelAnimationFrame1` as any] || window[`${prefix}CancelRequestAnimationFrame` as any]
}
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function (callback: (arg0: number) => void) {
const currTime = new Date().getTime()
// 为了使setTimteout的尽可能的接近每秒60帧的效果
const timeToCall = Math.max(0, 16 - (currTime - lastTime))
const id = window.setTimeout(() => {
callback(currTime + timeToCall)
}, timeToCall)
lastTime = currTime + timeToCall
return id
}
cancelAnimationFrame = function (id: number | undefined) {
window.clearTimeout(id)
}
}
}
export { requestAnimationFrame, cancelAnimationFrame }
页面上的使用:
<CountTo :endVal="item.count" />
组件参数配置:
| Property(属性) | Description(描述) | Type(类型) | Default(默认值 |
|---|---|---|---|
| startVal | 开始值 | Number | 0 |
| endVal | 结束值 | Number | 2023 |
| duration | 动画时间,以毫秒为单位 | Number | 3000 |
| autoPlay | 自动播放 | Boolean | true |
| decimals | 保留的小数位 | Number | 0 |
| decimal | 十进制分割 | String | . |
| separator | 分隔符 | String | , |
| prefix | 前缀 | String | '' |
| suffix | 后缀 | String | '' |
| useEasing | 开启缓和动画 | Boolean | true |
| easingFn | 缓和动画回调 | Function | - |
组件方法:
| Function Name(函数名称) | Description(描述) |
|---|---|
| mountedCallback | 挂载以后返回回调 |
| start | 开始计数 |
| pause | 暂停计数 |
| reset | 重置 |