NumberRoll.vue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <template>
  2. <ul
  3. class="base-num-roll"
  4. :style="{ height: rollHeightRef }"
  5. >
  6. <li
  7. v-for="index in liTranslate.length"
  8. :key="index"
  9. :class="customClassRef"
  10. >
  11. <div
  12. :style="[
  13. liTranslate[index - 1],
  14. { 'transition-duration': +durationRef / 1000 + 's' },
  15. { 'transition-timing-function': transitionTimingFunctionRef },
  16. ]"
  17. >
  18. <p
  19. v-for="n in 10"
  20. :key="n"
  21. :style="{ height: rollHeightRef }"
  22. >
  23. {{ reverseDirectionRef ? 10 - n : n - 1 }}
  24. </p>
  25. </div>
  26. </li>
  27. </ul>
  28. </template>
  29. <script setup lang="ts">
  30. import { ref, computed, onMounted, watch, toRefs } from 'vue'
  31. /**
  32. * props
  33. */
  34. // eslint-disable-next-line no-undef
  35. const props = defineProps({
  36. startNumber: {
  37. type: [Number, String],
  38. default: 0,
  39. validator: (value: number | string) => Number.isInteger(+value) && +value >= 0,
  40. },
  41. endNumber: {
  42. type: [Number, String],
  43. default: 0,
  44. validator: (value: number | string) => Number.isInteger(+value) && +value >= 0,
  45. },
  46. duration: {
  47. type: [Number, String],
  48. default: 3000,
  49. validator: (value: number | string) => Number.isInteger(+value) && +value >= 0,
  50. },
  51. transitionTimingFunction: {
  52. type: String,
  53. default: 'ease-in-out',
  54. validator: (value: string) =>
  55. ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'].includes(value) || value.includes('cubic-bezier'),
  56. },
  57. minLength: {
  58. type: [Number, String],
  59. default: 0,
  60. validator: (value: number | string) => Number.isInteger(+value) && +value >= 0,
  61. },
  62. rollHeight: {
  63. type: String,
  64. required: true,
  65. validator: (value: string) => /^\d+(px|rem|em|%)+$/g.test(value),
  66. },
  67. reverseDirection: {
  68. type: Boolean,
  69. default: false,
  70. },
  71. autoplay: {
  72. type: Boolean,
  73. default: false,
  74. },
  75. customClass: {
  76. type: String,
  77. default: '',
  78. },
  79. })
  80. const {
  81. // fixme 如果模板直接使用同名的变量,会报错,所以加了`Ref`
  82. startNumber: startNumberRef,
  83. endNumber: endNumberRef,
  84. duration: durationRef,
  85. transitionTimingFunction: transitionTimingFunctionRef,
  86. minLength: minLengthRef,
  87. rollHeight: rollHeightRef,
  88. reverseDirection: reverseDirectionRef,
  89. autoplay: autoplayRef,
  90. customClass: customClassRef,
  91. } = toRefs(props)
  92. /**
  93. * data
  94. */
  95. const liTranslate = ref<{ transform: string }[]>([])
  96. const rollNumber = computed(() => endNumberRef.value.toString().padStart(+minLengthRef.value, '0'))
  97. const rollHeightUnit = computed(() => rollHeightRef.value.replace(/\d/g, ''))
  98. const rollHeightNumber = computed(() => +rollHeightRef.value.replace(new RegExp(rollHeightUnit.value, 'g'), ''))
  99. /**
  100. * watch
  101. */
  102. watch([startNumberRef, rollHeightRef, minLengthRef, reverseDirectionRef], init, { immediate: true })
  103. /**
  104. * life cycle
  105. */
  106. onMounted(() => autoplayRef.value && setTimeout(start, 1000))
  107. /**
  108. * methods
  109. */
  110. // eslint-disable-next-line no-undef
  111. defineExpose({ start, reset: init })
  112. function init() {
  113. liTranslate.value.length = 0
  114. startNumberRef.value
  115. .toString()
  116. .padStart(+minLengthRef.value, '0')
  117. .split('')
  118. .forEach((number, idx) => setLiTranslate(idx, +number))
  119. }
  120. function start() {
  121. liTranslate.value.length = 0
  122. rollNumber.value.split('').forEach((number, idx) => setLiTranslate(idx, +number))
  123. }
  124. function setLiTranslate(idx: number, number: number) {
  125. // Vue3
  126. liTranslate.value[idx] = {
  127. transform: `translateY(${
  128. (reverseDirectionRef.value ? (+number - 9) * rollHeightNumber.value : -number * rollHeightNumber.value) +
  129. rollHeightUnit.value
  130. })`,
  131. }
  132. }
  133. </script>
  134. <style lang="scss">
  135. .base-num-roll {
  136. margin: 0;
  137. padding: 0;
  138. list-style: none;
  139. overflow: hidden;
  140. display: inline-flex;
  141. li {
  142. transition-property: transform;
  143. box-sizing: border-box;
  144. margin-top: 0 !important;
  145. margin-bottom: 0 !important;
  146. p {
  147. margin: 0;
  148. padding: 0;
  149. display: flex;
  150. justify-content: center;
  151. align-items: center;
  152. box-sizing: border-box;
  153. }
  154. }
  155. }
  156. </style>