u-count-to.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <template>
  2. <text
  3. class="u-count-num"
  4. :style="{
  5. fontSize: addUnit(fontSize),
  6. fontWeight: bold ? 'bold' : 'normal',
  7. color: color
  8. }"
  9. >{{ displayValue }}</text>
  10. </template>
  11. <script>
  12. import { props } from './props';
  13. import { mpMixin } from '../../libs/mixin/mpMixin';
  14. import { mixin } from '../../libs/mixin/mixin';
  15. import { addUnit } from '../../libs/function/index';
  16. /**
  17. * countTo 数字滚动
  18. * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
  19. * @tutorial https://ijry.github.io/uview-plus/components/countTo.html
  20. * @property {String | Number} startVal 开始的数值,默认从0增长到某一个数(默认 0 )
  21. * @property {String | Number} endVal 要滚动的目标数值,必须 (默认 0 )
  22. * @property {String | Number} duration 滚动到目标数值的动画持续时间,单位为毫秒(ms) (默认 2000 )
  23. * @property {Boolean} autoplay 设置数值后是否自动开始滚动 (默认 true )
  24. * @property {String | Number} decimals 要显示的小数位数,见官网说明(默认 0 )
  25. * @property {Boolean} useEasing 滚动结束时,是否缓动结尾,见官网说明(默认 true )
  26. * @property {String} decimal 十进制分割 ( 默认 "." )
  27. * @property {String} color 字体颜色( 默认 '#606266' )
  28. * @property {String | Number} fontSize 字体大小,单位px( 默认 22 )
  29. * @property {Boolean} bold 字体是否加粗(默认 false )
  30. * @property {String} separator 千位分隔符,见官网说明
  31. * @event {Function} end 数值滚动到目标值时触发
  32. * @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
  33. */
  34. export default {
  35. name: 'u-count-to',
  36. data() {
  37. return {
  38. localStartVal: this.startVal,
  39. displayValue: this.formatNumber(this.startVal),
  40. printVal: null,
  41. paused: false, // 是否暂停
  42. localDuration: Number(this.duration),
  43. startTime: null, // 开始的时间
  44. timestamp: null, // 时间戳
  45. remaining: null, // 停留的时间
  46. rAF: null,
  47. lastTime: 0 // 上一次的时间
  48. };
  49. },
  50. mixins: [mpMixin, mixin,props],
  51. computed: {
  52. countDown() {
  53. return this.startVal > this.endVal;
  54. }
  55. },
  56. watch: {
  57. startVal() {
  58. this.autoplay && this.start();
  59. },
  60. endVal() {
  61. this.autoplay && this.start();
  62. }
  63. },
  64. mounted() {
  65. this.autoplay && this.start();
  66. },
  67. emits: ["end"],
  68. methods: {
  69. addUnit,
  70. easingFn(t, b, c, d) {
  71. return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
  72. },
  73. requestAnimationFrame(callback) {
  74. const currTime = new Date().getTime();
  75. // 为了使setTimteout的尽可能的接近每秒60帧的效果
  76. const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
  77. const id = setTimeout(() => {
  78. callback(currTime + timeToCall);
  79. }, timeToCall);
  80. this.lastTime = currTime + timeToCall;
  81. return id;
  82. },
  83. cancelAnimationFrame(id) {
  84. clearTimeout(id);
  85. },
  86. // 开始滚动数字
  87. start() {
  88. this.localStartVal = this.startVal;
  89. this.startTime = null;
  90. this.localDuration = this.duration;
  91. this.paused = false;
  92. this.rAF = this.requestAnimationFrame(this.count);
  93. },
  94. // 暂定状态,重新再开始滚动;或者滚动状态下,暂停
  95. reStart() {
  96. if (this.paused) {
  97. this.resume();
  98. this.paused = false;
  99. } else {
  100. this.stop();
  101. this.paused = true;
  102. }
  103. },
  104. // 暂停
  105. stop() {
  106. this.cancelAnimationFrame(this.rAF);
  107. },
  108. // 重新开始(暂停的情况下)
  109. resume() {
  110. if (!this.remaining) return
  111. this.startTime = 0;
  112. this.localDuration = this.remaining;
  113. this.localStartVal = this.printVal;
  114. this.requestAnimationFrame(this.count);
  115. },
  116. // 重置
  117. reset() {
  118. this.startTime = null;
  119. this.cancelAnimationFrame(this.rAF);
  120. this.displayValue = this.formatNumber(this.startVal);
  121. },
  122. count(timestamp) {
  123. if (!this.startTime) this.startTime = timestamp;
  124. this.timestamp = timestamp;
  125. const progress = timestamp - this.startTime;
  126. this.remaining = this.localDuration - progress;
  127. if (this.useEasing) {
  128. if (this.countDown) {
  129. this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
  130. } else {
  131. this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
  132. }
  133. } else {
  134. if (this.countDown) {
  135. this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
  136. } else {
  137. this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
  138. }
  139. }
  140. if (this.countDown) {
  141. this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
  142. } else {
  143. this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
  144. }
  145. this.displayValue = this.formatNumber(this.printVal) || 0;
  146. if (progress < this.localDuration) {
  147. this.rAF = this.requestAnimationFrame(this.count);
  148. } else {
  149. this.$emit('end');
  150. }
  151. },
  152. // 判断是否数字
  153. isNumber(val) {
  154. return !isNaN(parseFloat(val));
  155. },
  156. formatNumber(num) {
  157. // 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错
  158. num = Number(num);
  159. num = num.toFixed(Number(this.decimals));
  160. num += '';
  161. const x = num.split('.');
  162. let x1 = x[0];
  163. const x2 = x.length > 1 ? this.decimal + x[1] : '';
  164. const rgx = /(\d+)(\d{3})/;
  165. if (this.separator && !this.isNumber(this.separator)) {
  166. while (rgx.test(x1)) {
  167. x1 = x1.replace(rgx, '$1' + this.separator + '$2');
  168. }
  169. }
  170. return x1 + x2;
  171. },
  172. destroyed() {
  173. this.cancelAnimationFrame(this.rAF);
  174. }
  175. }
  176. };
  177. </script>
  178. <style lang="scss" scoped>
  179. @import "../../libs/css/components.scss";
  180. .u-count-num {
  181. /* #ifndef APP-NVUE */
  182. display: inline-flex;
  183. /* #endif */
  184. text-align: center;
  185. }
  186. </style>