| <template> | 
|     <text | 
|         class="u-count-num" | 
|         :style="{ | 
|             fontSize: $u.addUnit(fontSize), | 
|             fontWeight: bold ? 'bold' : 'normal', | 
|             color: color | 
|         }" | 
|     >{{ displayValue }}</text> | 
| </template> | 
|   | 
| <script> | 
|     import props from './props.js'; | 
| /** | 
|  * countTo 数字滚动 | 
|  * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。 | 
|  * @tutorial https://www.uviewui.com/components/countTo.html | 
|  * @property {String | Number}    startVal    开始的数值,默认从0增长到某一个数(默认 0 ) | 
|  * @property {String | Number}    endVal        要滚动的目标数值,必须 (默认 0 ) | 
|  * @property {String | Number}    duration    滚动到目标数值的动画持续时间,单位为毫秒(ms) (默认 2000 ) | 
|  * @property {Boolean}            autoplay    设置数值后是否自动开始滚动 (默认 true ) | 
|  * @property {String | Number}    decimals    要显示的小数位数,见官网说明(默认 0 ) | 
|  * @property {Boolean}            useEasing    滚动结束时,是否缓动结尾,见官网说明(默认 true ) | 
|  * @property {String}            decimal        十进制分割 ( 默认 "." ) | 
|  * @property {String}            color        字体颜色( 默认 '#606266' ) | 
|  * @property {String | Number}    fontSize    字体大小,单位px( 默认 22 ) | 
|  * @property {Boolean}            bold        字体是否加粗(默认 false ) | 
|  * @property {String}            separator    千位分隔符,见官网说明 | 
|  * @event {Function} end 数值滚动到目标值时触发 | 
|  * @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to> | 
|  */ | 
| export default { | 
|     name: 'u-count-to', | 
|     data() { | 
|         return { | 
|             localStartVal: this.startVal, | 
|             displayValue: this.formatNumber(this.startVal), | 
|             printVal: null, | 
|             paused: false, // 是否暂停 | 
|             localDuration: Number(this.duration), | 
|             startTime: null, // 开始的时间 | 
|             timestamp: null, // 时间戳 | 
|             remaining: null, // 停留的时间 | 
|             rAF: null, | 
|             lastTime: 0 // 上一次的时间 | 
|         }; | 
|     }, | 
|     mixins: [uni.$u.mpMixin, uni.$u.mixin,props], | 
|     computed: { | 
|         countDown() { | 
|             return this.startVal > this.endVal; | 
|         } | 
|     }, | 
|     watch: { | 
|         startVal() { | 
|             this.autoplay && this.start(); | 
|         }, | 
|         endVal() { | 
|             this.autoplay && this.start(); | 
|         } | 
|     }, | 
|     mounted() { | 
|         this.autoplay && this.start(); | 
|     }, | 
|     methods: { | 
|         easingFn(t, b, c, d) { | 
|             return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b; | 
|         }, | 
|         requestAnimationFrame(callback) { | 
|             const currTime = new Date().getTime(); | 
|             // 为了使setTimteout的尽可能的接近每秒60帧的效果 | 
|             const timeToCall = Math.max(0, 16 - (currTime - this.lastTime)); | 
|             const id = setTimeout(() => { | 
|                 callback(currTime + timeToCall); | 
|             }, timeToCall); | 
|             this.lastTime = currTime + timeToCall; | 
|             return id; | 
|         }, | 
|         cancelAnimationFrame(id) { | 
|             clearTimeout(id); | 
|         }, | 
|         // 开始滚动数字 | 
|         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() { | 
|             if (!this.remaining) return | 
|             this.startTime = 0; | 
|             this.localDuration = this.remaining; | 
|             this.localStartVal = this.printVal; | 
|             this.requestAnimationFrame(this.count); | 
|         }, | 
|         // 重置 | 
|         reset() { | 
|             this.startTime = null; | 
|             this.cancelAnimationFrame(this.rAF); | 
|             this.displayValue = this.formatNumber(this.startVal); | 
|         }, | 
|         count(timestamp) { | 
|             if (!this.startTime) this.startTime = timestamp; | 
|             this.timestamp = timestamp; | 
|             const progress = timestamp - this.startTime; | 
|             this.remaining = this.localDuration - progress; | 
|             if (this.useEasing) { | 
|                 if (this.countDown) { | 
|                     this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration); | 
|                 } else { | 
|                     this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration); | 
|                 } | 
|             } else { | 
|                 if (this.countDown) { | 
|                     this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration); | 
|                 } else { | 
|                     this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration); | 
|                 } | 
|             } | 
|             if (this.countDown) { | 
|                 this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal; | 
|             } else { | 
|                 this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal; | 
|             } | 
|             this.displayValue = this.formatNumber(this.printVal) || 0; | 
|             if (progress < this.localDuration) { | 
|                 this.rAF = this.requestAnimationFrame(this.count); | 
|             } else { | 
|                 this.$emit('end'); | 
|             } | 
|         }, | 
|         // 判断是否数字 | 
|         isNumber(val) { | 
|             return !isNaN(parseFloat(val)); | 
|         }, | 
|         formatNumber(num) { | 
|             // 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错 | 
|             num = Number(num); | 
|             num = num.toFixed(Number(this.decimals)); | 
|             num += ''; | 
|             const x = num.split('.'); | 
|             let x1 = x[0]; | 
|             const x2 = x.length > 1 ? this.decimal + x[1] : ''; | 
|             const rgx = /(\d+)(\d{3})/; | 
|             if (this.separator && !this.isNumber(this.separator)) { | 
|                 while (rgx.test(x1)) { | 
|                     x1 = x1.replace(rgx, '$1' + this.separator + '$2'); | 
|                 } | 
|             } | 
|             return x1 + x2; | 
|         }, | 
|         destroyed() { | 
|             this.cancelAnimationFrame(this.rAF); | 
|         } | 
|     } | 
| }; | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
| @import "../../libs/css/components.scss"; | 
|   | 
| .u-count-num { | 
|     /* #ifndef APP-NVUE */ | 
|     display: inline-flex; | 
|     /* #endif */ | 
|     text-align: center; | 
| } | 
| </style> |