index.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import {
  2. defineComponent,
  3. reactive,
  4. computed,
  5. watch,
  6. onMounted,
  7. unref
  8. } from "vue";
  9. import { countToProps } from "./props";
  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 state = reactive<{
  17. localStartVal: number;
  18. printVal: number | null;
  19. displayValue: string;
  20. paused: boolean;
  21. localDuration: number | null;
  22. startTime: number | null;
  23. timestamp: number | null;
  24. rAF: any;
  25. remaining: number | null;
  26. color: string;
  27. fontSize: string;
  28. }>({
  29. localStartVal: props.startVal,
  30. displayValue: formatNumber(props.startVal),
  31. printVal: null,
  32. paused: false,
  33. localDuration: props.duration,
  34. startTime: null,
  35. timestamp: null,
  36. remaining: null,
  37. rAF: null,
  38. color: null,
  39. fontSize: "16px"
  40. });
  41. const getCountDown = computed(() => {
  42. return props.startVal > props.endVal;
  43. });
  44. watch([() => props.startVal, () => props.endVal], () => {
  45. if (props.autoplay) {
  46. start();
  47. }
  48. });
  49. function start() {
  50. const { startVal, duration, color, fontSize } = props;
  51. state.localStartVal = startVal;
  52. state.startTime = null;
  53. state.localDuration = duration;
  54. state.paused = false;
  55. state.color = color;
  56. state.fontSize = fontSize;
  57. state.rAF = requestAnimationFrame(count);
  58. }
  59. // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
  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. // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
  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(
  95. progress,
  96. 0,
  97. state.localStartVal - endVal,
  98. state.localDuration as number
  99. );
  100. } else {
  101. state.printVal = easingFn(
  102. progress,
  103. state.localStartVal,
  104. endVal - state.localStartVal,
  105. state.localDuration as number
  106. );
  107. }
  108. } else {
  109. if (unref(getCountDown)) {
  110. state.printVal =
  111. state.localStartVal -
  112. (state.localStartVal - endVal) *
  113. (progress / (state.localDuration as number));
  114. } else {
  115. state.printVal =
  116. state.localStartVal +
  117. (endVal - state.localStartVal) *
  118. (progress / (state.localDuration as number));
  119. }
  120. }
  121. if (unref(getCountDown)) {
  122. state.printVal = state.printVal < endVal ? endVal : state.printVal;
  123. } else {
  124. state.printVal = state.printVal > endVal ? endVal : state.printVal;
  125. }
  126. state.displayValue = formatNumber(state.printVal);
  127. if (progress < (state.localDuration as number)) {
  128. state.rAF = requestAnimationFrame(count);
  129. } else {
  130. emit("callback");
  131. }
  132. }
  133. function formatNumber(num: number | string) {
  134. const { decimals, decimal, separator, suffix, prefix } = props;
  135. num = Number(num).toFixed(decimals);
  136. num += "";
  137. const x = num.split(".");
  138. let x1 = x[0];
  139. const x2 = x.length > 1 ? decimal + x[1] : "";
  140. const rgx = /(\d+)(\d{3})/;
  141. if (separator && !isNumber(separator)) {
  142. while (rgx.test(x1)) {
  143. x1 = x1.replace(rgx, "$1" + separator + "$2");
  144. }
  145. }
  146. return prefix + x1 + x2 + suffix;
  147. }
  148. onMounted(() => {
  149. if (props.autoplay) {
  150. start();
  151. }
  152. emit("mounted");
  153. });
  154. return () => (
  155. <>
  156. <span
  157. style={{
  158. color: props.color,
  159. fontSize: props.fontSize
  160. }}>
  161. {state.displayValue}
  162. </span>
  163. </>
  164. );
  165. }
  166. });