232 lines
6.3 KiB
Vue
232 lines
6.3 KiB
Vue
<template>
|
||
<view
|
||
class="tn-count-num-class tn-count-num"
|
||
:class="[fontColorClass]"
|
||
:style="{
|
||
fontSize: fontSizeStyle || '50rpx',
|
||
fontWeight: bold ? 'bold' : 'normal',
|
||
color: fontColorStyle || '#080808'
|
||
}"
|
||
>
|
||
{{ displayValue }}
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||
export default {
|
||
name: 'tn-count-to',
|
||
mixins: [componentsColorMixin],
|
||
props: {
|
||
// 开始的数值,默认为0
|
||
startVal: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
// 结束目标数值
|
||
endVal: {
|
||
type: Number,
|
||
default: 0,
|
||
required: true
|
||
},
|
||
// 是否自动开始
|
||
autoplay: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 滚动到目标值的持续时间,单位为毫秒
|
||
duration: {
|
||
type: Number,
|
||
default: 2000
|
||
},
|
||
// 是否在即将结束的时候使用缓慢滚动的效果
|
||
useEasing: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 显示的小数位数
|
||
decimals: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
// 十进制的分割符
|
||
decimalSeparator: {
|
||
type: String,
|
||
default: '.'
|
||
},
|
||
// 千分位的分隔符
|
||
// 类似金额的分割(¥23,321.05中的",")
|
||
thousandthsSeparator: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 是否显示加粗字体
|
||
bold: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
},
|
||
computed: {
|
||
countDown() {
|
||
return this.startVal > this.endVal
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
localStartVal: this.startVal,
|
||
localDuration: this.duration,
|
||
// 显示的数值
|
||
displayValue: this.formatNumber(this.startVal),
|
||
// 打印的数值
|
||
printValue: null,
|
||
// 是否暂停
|
||
paused: false,
|
||
// 开始时间戳
|
||
startTime: null,
|
||
// 停留时间戳
|
||
remainingTime: null,
|
||
// 当前时间戳
|
||
timestamp: null,
|
||
// 上一次的时间戳
|
||
lastTime: 0,
|
||
rAF: null
|
||
}
|
||
},
|
||
watch: {
|
||
startVal() {
|
||
this.autoplay && this.start()
|
||
},
|
||
endVal() {
|
||
this.autoplay && this.start()
|
||
}
|
||
},
|
||
mounted() {
|
||
this.autoplay && this.start()
|
||
},
|
||
methods: {
|
||
// 开始滚动
|
||
start() {
|
||
this.localStartVal = this.startVal
|
||
this.startTime = null
|
||
this.localDuration = this.duration
|
||
this.paused = false
|
||
this.rAF = this.requestAnimationFrame(this.count)
|
||
},
|
||
// 重新开始
|
||
reStart() {
|
||
if (this.paused) {
|
||
this.resume()
|
||
this.paused = false
|
||
} else {
|
||
this.stop()
|
||
this.paused = true
|
||
}
|
||
},
|
||
// 停止
|
||
stop() {
|
||
this.cancelAnimationFrame(this.rAF)
|
||
},
|
||
// 恢复
|
||
resume() {
|
||
this.startTime = null
|
||
this.localDuration = this.remainingTime
|
||
this.localStartVal = this.printValue
|
||
this.requestAnimationFrame(this.count)
|
||
},
|
||
// 重置
|
||
reset() {
|
||
this.startTime = null
|
||
this.cnacelAnimationFrame(this.rAF)
|
||
this.displayValue = this.formatNumber(this.startVal)
|
||
},
|
||
// 销毁组件
|
||
destroyed() {
|
||
this.cancelAnimationFrame(this.rAF)
|
||
},
|
||
// 累加时间
|
||
count(timestamp) {
|
||
if (!this.startTime) this.startTime = timestamp
|
||
this.timestamp = timestamp
|
||
const progress = timestamp - this.startTime
|
||
this.remainingTime = this.localDuration - progress
|
||
if (this.useEasing) {
|
||
if (this.countDown) {
|
||
this.printValue = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
|
||
} {
|
||
this.printValue = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
|
||
}
|
||
} else {
|
||
if (this.countDown) {
|
||
this.printValue = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
|
||
} else {
|
||
this.printValue = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
|
||
}
|
||
}
|
||
if (this.countDown) {
|
||
this.printValue = this.printValue < this.endVal ? this.endVal : this.printValue
|
||
} else {
|
||
this.printValue = this.printValue > this.endVal ? this.endVal : this.printValue
|
||
}
|
||
|
||
this.displayValue = this.formatNumber(this.printValue)
|
||
if (progress < this.localDuration) {
|
||
this.rAF = this.requestAnimationFrame(this.count)
|
||
} else {
|
||
this.$emit('end')
|
||
}
|
||
},
|
||
// 缓动时间计算
|
||
easingFn(t, b, c, d) {
|
||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||
},
|
||
// 请求帧动画
|
||
requestAnimationFrame(cb) {
|
||
const currentTime = new Date().getTime()
|
||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||
const timeToCall = Math.max(0, 16 - (currentTime - this.lastTime))
|
||
const timerId = setTimeout(() => {
|
||
cb && cb(currentTime + timeToCall)
|
||
}, timeToCall)
|
||
this.lastTime = currentTime + timeToCall
|
||
return timerId
|
||
},
|
||
// 清除帧动画
|
||
clearAnimationFrame(timerId) {
|
||
clearTimeout(timerId)
|
||
},
|
||
// 格式化数值
|
||
formatNumber(number) {
|
||
const reg = /(\d+)(\d{3})/
|
||
number = Number(number)
|
||
number = number.toFixed(Number(this.decimals))
|
||
number += ''
|
||
const numberArray = number.split('.')
|
||
let num1 = numberArray[0]
|
||
const num2 = numberArray.length > 1 ? this.decimalSeparator + numberArray[1] : ''
|
||
|
||
if (this.thousandthsSeparator && !this.isNumber(this.thousandthsSeparator)) {
|
||
while(reg.test(num1)) {
|
||
num1 = num1.replace(reg, '$1' + this.thousandthsSeparator + '$2')
|
||
}
|
||
}
|
||
return num1 + num2
|
||
},
|
||
// 判断是否为数字
|
||
isNumber(val) {
|
||
return !isNaN(parseFloat(val))
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
|
||
.tn-count-num {
|
||
/* #ifndef APP-NVUE */
|
||
display: inline-flex;
|
||
/* #endif */
|
||
text-align: center;
|
||
line-height: 1;
|
||
}
|
||
</style>
|