index.vue 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <template>
  2. <span>
  3. {{ displayValue }}
  4. </span>
  5. </template>
  6. <script setup lang="ts" name="CountTo">
  7. import { reactive, computed, watch, onMounted, unref, toRef } from 'vue'
  8. import { countToProps } from './props'
  9. import { isNumber } from '@/utils/validate'
  10. import { requestAnimationFrame, cancelAnimationFrame } from '@/utils/animation'
  11. const props = defineProps(countToProps)
  12. const emit = defineEmits(['mounted', 'callback'])
  13. defineExpose({
  14. pauseResume,
  15. reset
  16. })
  17. const state = reactive<{
  18. localStartVal: number
  19. printVal: number | null
  20. displayValue: string
  21. paused: boolean
  22. localDuration: number | null
  23. startTime: number | null
  24. timestamp: number | null
  25. rAF: any
  26. remaining: number | null
  27. }>({
  28. localStartVal: props.startVal,
  29. displayValue: formatNumber(props.startVal),
  30. printVal: null,
  31. paused: false,
  32. localDuration: props.duration,
  33. startTime: null,
  34. timestamp: null,
  35. remaining: null,
  36. rAF: null
  37. })
  38. const displayValue = toRef(state, 'displayValue')
  39. onMounted(() => {
  40. if (props.autoplay) {
  41. start()
  42. }
  43. emit('mounted')
  44. })
  45. const getCountDown = computed(() => {
  46. return props.startVal > props.endVal
  47. })
  48. watch([() => props.startVal, () => props.endVal], () => {
  49. if (props.autoplay) {
  50. start()
  51. }
  52. })
  53. function start() {
  54. const { startVal, duration } = props
  55. state.localStartVal = startVal
  56. state.startTime = null
  57. state.localDuration = duration
  58. state.paused = false
  59. state.rAF = requestAnimationFrame(count)
  60. }
  61. function pauseResume() {
  62. if (state.paused) {
  63. resume()
  64. state.paused = false
  65. } else {
  66. pause()
  67. state.paused = true
  68. }
  69. }
  70. function pause() {
  71. cancelAnimationFrame(state.rAF)
  72. }
  73. function resume() {
  74. state.startTime = null
  75. state.localDuration = +(state.remaining as number)
  76. state.localStartVal = +(state.printVal as number)
  77. requestAnimationFrame(count)
  78. }
  79. function reset() {
  80. state.startTime = null
  81. cancelAnimationFrame(state.rAF)
  82. state.displayValue = formatNumber(props.startVal)
  83. }
  84. function count(timestamp: number) {
  85. const { useEasing, easingFn, endVal } = props
  86. if (!state.startTime) state.startTime = timestamp
  87. state.timestamp = timestamp
  88. const progress = timestamp - state.startTime
  89. state.remaining = (state.localDuration as number) - progress
  90. if (useEasing) {
  91. if (unref(getCountDown)) {
  92. state.printVal =
  93. state.localStartVal -
  94. easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
  95. } else {
  96. state.printVal = easingFn(
  97. progress,
  98. state.localStartVal,
  99. endVal - state.localStartVal,
  100. state.localDuration as number
  101. )
  102. }
  103. } else {
  104. if (unref(getCountDown)) {
  105. state.printVal =
  106. state.localStartVal -
  107. (state.localStartVal - endVal) * (progress / (state.localDuration as number))
  108. } else {
  109. state.printVal =
  110. state.localStartVal +
  111. (endVal - state.localStartVal) * (progress / (state.localDuration as number))
  112. }
  113. }
  114. if (unref(getCountDown)) {
  115. state.printVal = state.printVal < endVal ? endVal : state.printVal
  116. } else {
  117. state.printVal = state.printVal > endVal ? endVal : state.printVal
  118. }
  119. state.displayValue = formatNumber(state.printVal)
  120. if (progress < (state.localDuration as number)) {
  121. state.rAF = requestAnimationFrame(count)
  122. } else {
  123. emit('callback')
  124. }
  125. }
  126. function formatNumber(num: number | string) {
  127. const { decimals, decimal, separator, suffix, prefix } = props
  128. num = Number(num).toFixed(decimals)
  129. num += ''
  130. const x = num.split('.')
  131. let x1 = x[0]
  132. const x2 = x.length > 1 ? decimal + x[1] : ''
  133. const rgx = /(\d+)(\d{3})/
  134. if (separator && !isNumber(separator)) {
  135. while (rgx.test(x1)) {
  136. x1 = x1.replace(rgx, '$1' + separator + '$2')
  137. }
  138. }
  139. return prefix + x1 + x2 + suffix
  140. }
  141. </script>