123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- <template>
- <span>
- {{ displayValue }}
- </span>
- </template>
- <script setup lang="ts" name="CountTo">
- import { reactive, computed, watch, onMounted, unref, toRef } from 'vue'
- import { countToProps } from './props'
- import { isNumber } from '@/utils/validate'
- import { requestAnimationFrame, cancelAnimationFrame } from '@/utils/animation'
- const props = defineProps(countToProps)
- const emit = defineEmits(['mounted', 'callback'])
- defineExpose({
- pauseResume,
- reset
- })
- const state = reactive<{
- localStartVal: number
- printVal: number | null
- displayValue: string
- paused: boolean
- localDuration: number | null
- startTime: number | null
- timestamp: number | null
- rAF: any
- remaining: number | null
- }>({
- localStartVal: props.startVal,
- displayValue: formatNumber(props.startVal),
- printVal: null,
- paused: false,
- localDuration: props.duration,
- startTime: null,
- timestamp: null,
- remaining: null,
- rAF: null
- })
- const displayValue = toRef(state, 'displayValue')
- onMounted(() => {
- if (props.autoplay) {
- start()
- }
- emit('mounted')
- })
- const getCountDown = computed(() => {
- return props.startVal > props.endVal
- })
- watch([() => props.startVal, () => props.endVal], () => {
- if (props.autoplay) {
- start()
- }
- })
- function start() {
- const { startVal, duration } = props
- state.localStartVal = startVal
- state.startTime = null
- state.localDuration = duration
- state.paused = false
- state.rAF = requestAnimationFrame(count)
- }
- function pauseResume() {
- if (state.paused) {
- resume()
- state.paused = false
- } else {
- pause()
- state.paused = true
- }
- }
- function pause() {
- cancelAnimationFrame(state.rAF)
- }
- function resume() {
- state.startTime = null
- state.localDuration = +(state.remaining as number)
- state.localStartVal = +(state.printVal as number)
- requestAnimationFrame(count)
- }
- function reset() {
- state.startTime = null
- cancelAnimationFrame(state.rAF)
- state.displayValue = formatNumber(props.startVal)
- }
- function count(timestamp: number) {
- const { useEasing, easingFn, endVal } = props
- if (!state.startTime) state.startTime = timestamp
- state.timestamp = timestamp
- const progress = timestamp - state.startTime
- state.remaining = (state.localDuration as number) - progress
- if (useEasing) {
- if (unref(getCountDown)) {
- state.printVal =
- state.localStartVal -
- easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
- } else {
- state.printVal = easingFn(
- progress,
- state.localStartVal,
- endVal - state.localStartVal,
- state.localDuration as number
- )
- }
- } else {
- if (unref(getCountDown)) {
- state.printVal =
- state.localStartVal -
- (state.localStartVal - endVal) * (progress / (state.localDuration as number))
- } else {
- state.printVal =
- state.localStartVal +
- (endVal - state.localStartVal) * (progress / (state.localDuration as number))
- }
- }
- if (unref(getCountDown)) {
- state.printVal = state.printVal < endVal ? endVal : state.printVal
- } else {
- state.printVal = state.printVal > endVal ? endVal : state.printVal
- }
- state.displayValue = formatNumber(state.printVal)
- if (progress < (state.localDuration as number)) {
- state.rAF = requestAnimationFrame(count)
- } else {
- emit('callback')
- }
- }
- function formatNumber(num: number | string) {
- const { decimals, decimal, separator, suffix, prefix } = props
- num = Number(num).toFixed(decimals)
- num += ''
- const x = num.split('.')
- let x1 = x[0]
- const x2 = x.length > 1 ? decimal + x[1] : ''
- const rgx = /(\d+)(\d{3})/
- if (separator && !isNumber(separator)) {
- while (rgx.test(x1)) {
- x1 = x1.replace(rgx, '$1' + separator + '$2')
- }
- }
- return prefix + x1 + x2 + suffix
- }
- </script>
|