数值滚动实现

# 实现思路
将数值转换成字符串并拆分每个字符,将字符遍历渲染到页面。如果是非数值则直接显示,数值类型则在当前位置生成0-9的列,根据当前数值计算y方向的位置偏移
千分位显示:千分位下默认只保留两位小数,整数部分通过正则num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')将每3位数字添加分隔符,即可
# 实现代码
<template>
<div class="roll-wrap" :style="{ fontSize: `${cellHeight}px` }">
<ul class="roll-box">
<slot name="prefix"></slot>
<li
class="roll-item"
v-for="(item, index) in numberArr"
:key="index"
:style="{ height: `${cellHeight}px`, lineHeight: `${cellHeight}px` }"
>
<!--小数点或其他情况-->
<div v-if="isNaN(parseFloat(item))" class="roll-num">{{ item }}</div>
<div v-else :style="getStyles(index)">
<!--数字0到9-->
<div
:style="{ height: `${cellHeight}px`, lineHeight: `${cellHeight}px` }"
v-for="(subItem, subIndex) in oneToNineArr"
:key="subIndex"
class="roll-num"
>
{{ subItem }}
</div>
</div>
</li>
<slot name="suffix"></slot>
</ul>
</div>
</template>
<script lang="ts" setup>
import { ref, onBeforeMount, watch } from 'vue'
const numFormat = (num: string) => {
const c = num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
return c
}
interface PropsType {
cellHeight?: number // 高度
rollNumber?: string | number // 当前数值
dur?: number // 动画持续时间
easeFn?: string // 动画
thousands?: boolean // 千分位 10000 => 10,000
}
const props = withDefaults(defineProps<PropsType>(), {
cellHeight: 30,
rollNumber: 0,
dur: 1500,
easeFn: 'ease',
thousands: false
})
const number = ref()
const numberArr = ref<Array<string>>([])
const numberOffsetArr = ref<Array<number>>([])
const oneToNineArr = ref([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
const resetState = (len: number) => {
const newArr = new Array(len).join(',').split(',')
numberOffsetArr.value = newArr.map(() => 0)
// 延迟执行动画
setTimeout(() => {
// 设置传入的数字下标对应偏移量,重新赋值
numberArr.value.forEach((num, i) => {
numberOffsetArr.value[i] = parseInt(num) * props.cellHeight
// this.$set(this.numberOffsetArr, i, num * this.cellHeight)
})
}, 30)
}
const getStyles = (index: number) => {
const style = {
transition: `${props.easeFn} ${props.dur}ms`,
transform: `translate(0%, -${numberOffsetArr.value[index]}px)`
}
return style
}
// 监听props
watch(
() => props.rollNumber,
(value) => {
// 格式化数字
let ruleValue = value + ''
if (ruleValue) {
let valueArr = ruleValue.split('.')
if(props.thousands) {
ruleValue = numFormat(valueArr[0])
if (valueArr[1]) {
ruleValue += '.' + valueArr[1].substring(0, 2)
}
}
}
number.value = `${ruleValue}`
numberArr.value = `${ruleValue}`.split('')
resetState(numberArr.value.length)
},
{
immediate: true
}
)
onBeforeMount(() => {
number.value = props.rollNumber + ''
})
</script>
<style lang="less" scoped>
.roll-wrap {
ul.roll-box {
display: flex;
padding: 0;
margin: 0;
text-align: center;
overflow: hidden;
}
li {
overflow: hidden;
.roll-num {
font-size: 32px;
font-weight: 600;
color: #fff;
letter-spacing: 2px;
}
}
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
上次更新: 2025/09/05, 8:09:00