index.vue 4.7 KB

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