对于数字滚动特效在一些大屏项目里是非常受欢迎的
写在前面
参考:海的对岸
链接:juejin.cn/post/702141…
参考:你可能需要这样的大屏数字滚动效果 链接:juejin.cn/post/684490…
实现思路
- 用户传入需要进行滚动的数字(数值)
- 当组件在页面渲染出来后变会从0依次递增到该数值
- 用户可以传入time即时间去控制从0递增到该值的时长
- 用户也可以传入isThousandthPercentile来实现数值是否需要以千分位形式进行显示
把第二步拆开来,用代码来表达,就是“
-
a. 设置一个
步进值(增量)step, -
b. 整个计时器
time,设置一个开始值start,start和 传过来的具体值(value)比较,start比value小,继续使用计时器time,给start加上增量step,再比较start和value的大小,比到start比value大的时候,结束
具体代码
countToNumber.vue
<template>
<div class="number-grow-warp">
<span
ref="numberGrow"
:data-time="time"
class="number-grow"
:data-value="value"
>0</span
>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default() {
return 720;
},
},
time: {
type: Number,
default() {
return 1;
},
},
isThousandthPercentile: {
type: Boolean,
default() {
return true;
},
},
},
data() {
return {};
},
mounted() {
this.numberGrow(this.$refs.numberGrow);
},
watch: {
value: {
immediate: true,
handler(newVal, oldVal) {
this.$nextTick(() => {
this.numberGrow(this.$refs.numberGrow);
});
},
},
},
methods: {
// 文字变化效果
numberGrow(ele) {
//【调速度 1 ,步进值, 即每次跳的时候,增加的一个增量】
let step = parseInt((this.value * 100) / (this.time * 1000));
// 设置当前值:计时器持续运行时,每次页面上显示的跳动值
let current = 0;
// 设置开始值
let start = 0;
// 设置定时器,用来反复横跳的
let t = setInterval(() => {
// 每次增加一点步进值
start += step;
// 开始值大于传过来的的值,说明 到点了,不用 继续横跳了
if (start > this.value) {
clearInterval(t);
// 把穿过的值赋给start,结束
start = this.value;
// 清掉计时器
t = null;
}
if (start == 0) {
start = this.value;
clearInterval(t);
}
// 当前值等于开始值,那就结束
if (this.value === 0) {
return;
}
current = start;
// 正则
if (this.isThousandthPercentile) {
ele.innerHTML = current
.toString()
.replace(/(\d)(?=(?:\d{3}[+]?)+$)/g, "$1,");
} else {
ele.innerHTML = current.toString();
}
}, this.time * 100); // 【这里调速度 2, 通俗地讲,这里是页面上,肉眼能看到的跳动频率】
},
},
};
</script>
<style lang="scss" scoped>
.number-grow-warp {
transform: translateZ(0);
}
.number-grow {
display: block;
}
</style>
使用方式:
<div class="item-name">
<countToNumber
:value="item.VALUE"
:time="2"
:isThousandthPercentile="false"
/>
<span class="item-unit">/{{ item.UNIT }}</span>
</div>
export default {
components: {
countToNumber,
},
}
实现方式二
<template>
<div class="chartNum">
<h3 class="orderTitle">XX模块展示:</h3>
<div class="box-item">
<li :class="{'number-item': !isNaN(item), 'mark-item': isNaN(item) }"
v-for="(item,index) in orderNum"
:key="index">
<span v-if="!isNaN(item)">
<i ref="numberItem" :myIndex="index" :myValue="item">0123456789</i>
</span>
<span class="comma" v-else>{{item}}</span>
</li>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number, // 具体数值
default() {
return 0;
},
},
time: {
type: Number, // 滚动要花的时间,单位秒
default() {
return 3;
},
},
},
data() {
return {
orderNum: ['0', '0', ',', '0', '0', '0', ',', '0', '0', '0'], // 默认订单总数
}
},
mounted() {
this.toOrderNum(this.value) // 这里输入数字即可调用
this.increaseNumber(this.time);
},
methods: {
// 定时增长数字
increaseNumber (time) {
let self = this
this.timer = setInterval(() => {
self.newNumber = self.newNumber + self.getRandomNumber(1, 100)
self.setNumberTransform()
}, time * 1000)
},
// 设置文字滚动
setNumberTransform() {
// 拿到数字的ref,计算元素数量 numberItems数组自身顺序有问题,需要处理成和数值保持一致
const numberItems = this.$refs.numberItem
numberItems.sort((a, b) => {
// 如果value相同,按照value的降序
if (a.attributes.myIndex.value === b.attributes.myIndex.value) {
return b.attributes.myIndex.value - a.attributes.myIndex.value;
}
return a.attributes.myIndex.value - b.attributes.myIndex.value;
});
// 在 template 里面加上 myIndex 和 myValue 属性,是因为有次发现,显示出来的结果不准确
// 经排查,发现是 numberItems字段里面获取的 html元素顺序不对,所以重新给他排好序
// 注意:numberItems字段里面获取的 html元素顺序不对 是偶尔发生的现象,但是为了安全起见,还是处理下
const numberArr = this.orderNum.filter(item => !isNaN(item))
// 结合CSS 对数字字符进行滚动,显示订单数量
for (let index = 0; index < numberItems.length; index++) {
const elem = numberItems[index]
elem.style.transform = `translate(-50%, -${numberArr[index] * 10}%)`
}
},
getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
},
// 处理传过来的具体值value
toOrderNum(num) {
num = num.toString()
// 把具体值value变成字符串
if (num.length < 8) {
num = '0' + num // 如未满八位数,添加"0"补位
this.toOrderNum(num) // 递归添加"0"补位
} else if (num.length === 8) {
// 具体值value中加入逗号
num = num.slice(0, 2) + ',' + num.slice(2, 5) + ',' + num.slice(5, 8)
this.orderNum = num.split('') // 将其便变成数据,渲染至滚动数组
} else {
// 具体值value数字超过八位显示异常
this.$message.warning('xxx数量过大,显示异常,请联系后台管理员')
}
},
}
}
</script>
<style scoped lang='scss'>
/*具体值value总量滚动数字设置*/
.box-item {
position: relative;
height: 100px;
font-size: 54px;
line-height: 41px;
text-align: center;
list-style: none;
color: #2D7CFF;
writing-mode: vertical-lr;
text-orientation: upright;
/*文字禁止编辑*/
-moz-user-select: none; /*火狐*/
-webkit-user-select: none; /*webkit浏览器*/
-ms-user-select: none; /*IE10*/
-khtml-user-select: none; /*早期浏览器*/
user-select: none;
/* overflow: hidden; */
}
/* 默认逗号设置 */
.mark-item {
width: 10px;
height: 100px;
margin-right: 5px;
line-height: 10px;
font-size: 48px;
position: relative;
& > span {
position: absolute;
width: 100%;
bottom: 0;
writing-mode: vertical-rl;
text-orientation: upright;
}
}
/*滚动数字设置*/
.number-item {
width: 41px;
height: 75px;
background: #ccc;
list-style: none;
margin-right: 5px;
background:rgba(250,250,250,1);
border-radius:4px;
border:1px solid rgba(221,221,221,1);
& > span {
position: relative;
display: inline-block;
margin-right: 10px;
width: 100%;
height: 100%;
writing-mode: vertical-rl;
text-orientation: upright;
overflow: hidden;
& > i {
font-style: normal;
position: absolute;
top: 11px;
left: 50%;
transform: translate(-50%,0);
transition: transform 1s ease-in-out;
letter-spacing: 10px;
}
}
}
.number-item:last-child {
margin-right: 0;
}
</style>