CountTo.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. <template>
  2. <VerticalScrollText
  3. v-if="type === 'scroller'"
  4. :numberString="finalString"
  5. :animDuration="duration"
  6. v-bind="props"
  7. />
  8. <Text v-else v-bind="props" :text="finalString" />
  9. </template>
  10. <script setup lang="ts">
  11. import Text, { type TextProps } from '@/components/basic/Text.vue';
  12. import VerticalScrollText from '@/components/typography/VerticalScrollText.vue';
  13. import { FormatUtils } from '@imengyu/imengyu-utils';
  14. import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
  15. export interface CountToProps extends TextProps {
  16. /**
  17. * 开始值
  18. * @default 0
  19. */
  20. startValue?: number;
  21. /**
  22. * 结束值
  23. */
  24. endValue: number;
  25. /**
  26. * 持续时间
  27. * @default 3000
  28. */
  29. duration?: number;
  30. /**
  31. * 是否将数字转换为千分符
  32. * @default false
  33. */
  34. thousand?: boolean;
  35. /**
  36. * 数字如果不足该位数,则在前面补0
  37. */
  38. numberCount?: number,
  39. /**
  40. * 保留小数位数
  41. * @default 0
  42. */
  43. decimalCount?: number,
  44. /**
  45. * 效果类型。默认:text
  46. * * text 普通文字切换效果
  47. * * scroller 文字上下滚动效果
  48. */
  49. type?: 'text'|'scroller';
  50. }
  51. export interface CountToInstance {
  52. restart: () => void;
  53. }
  54. const props = withDefaults(defineProps<CountToProps>(), {
  55. startValue: 0,
  56. endValue: 0,
  57. duration: 3000,
  58. thousand: false,
  59. numberCount: 0,
  60. decimalCount: 0,
  61. type: 'text',
  62. });
  63. const value = ref(0);
  64. const interval = ref(0);
  65. const speed = computed(() =>
  66. Math.max(1, Math.floor(Math.abs(props.startValue - props.endValue) / (props.duration / 50)))
  67. );
  68. function startAnim() {
  69. if (interval.value > 0)
  70. clearInterval(interval.value);
  71. if (props.startValue < props.endValue) {
  72. //+
  73. interval.value = setInterval(() => {
  74. if (value.value >= props.endValue) {
  75. clearInterval(interval.value);
  76. interval.value = 0;
  77. value.value = props.endValue;
  78. }
  79. else
  80. value.value = value.value + speed.value;
  81. }, 50) as unknown as number;
  82. } else {
  83. //-
  84. interval.value = setInterval(() => {
  85. if (value.value <= props.endValue) {
  86. clearInterval(interval.value);
  87. interval.value = 0;
  88. value.value = props.endValue;
  89. }
  90. else
  91. value.value = value.value - speed.value;
  92. }, 50) as unknown as number;
  93. }
  94. }
  95. const finalString = computed(() => {
  96. let valueString = props.decimalCount > 0 ? value.value.toFixed(props.decimalCount) : value.value.toString();
  97. if (valueString.length < props.numberCount)
  98. valueString = FormatUtils.formatNumberWithZero(
  99. Math.floor(value.value),
  100. props.numberCount
  101. ) + (valueString.split('.')[1] ?? '');
  102. //转为文字
  103. return (props.thousand ? FormatUtils.formatNumberWithComma(valueString) : valueString);
  104. })
  105. function loadAnim() {
  106. if (props.type === 'text') {
  107. value.value = props.startValue;
  108. startAnim();
  109. } else {
  110. value.value = props.endValue;
  111. }
  112. }
  113. watch(() => [ props.startValue, props.endValue, props.type ], () => {
  114. loadAnim();
  115. });
  116. onMounted(() => {
  117. loadAnim();
  118. })
  119. onBeforeUnmount(() => {
  120. if (interval.value > 0) {
  121. clearInterval(interval.value);
  122. interval.value = 0;
  123. }
  124. });
  125. defineExpose<CountToInstance>({
  126. restart() {
  127. if (props.type === 'text') {
  128. value.value = props.startValue;
  129. startAnim();
  130. } else {
  131. value.value = props.startValue;
  132. setTimeout(() => value.value = props.endValue, 200);
  133. }
  134. },
  135. });
  136. </script>