u-count-to.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <template>
  2. <view
  3. class="u-count-num"
  4. :style="{
  5. fontSize: fontSize + 'rpx',
  6. fontWeight: bold ? 'bold' : 'normal',
  7. color: color
  8. }"
  9. >
  10. {{ displayValue }}
  11. </view>
  12. </template>
  13. <script>
  14. /**
  15. * countTo 数字滚动
  16. * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
  17. * @property {String Number} start-val 开始值
  18. * @property {String Number} end-val 结束值
  19. * @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000)
  20. * @property {Boolean} autoplay 是否自动开始滚动(默认true)
  21. * @property {String Number} decimals 要显示的小数位数,见官网说明(默认0)
  22. * @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true)
  23. * @property {String} separator 千位分隔符,见官网说明
  24. * @property {String} color 字体颜色(默认#303133)
  25. * @property {String Number} font-size 字体大小,单位rpx(默认50)
  26. * @property {Boolean} bold 字体是否加粗(默认false)
  27. * @event {Function} end 数值滚动到目标值时触发
  28. * @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
  29. */
  30. export default {
  31. name: 'u-count-to',
  32. props: {
  33. startVal: {
  34. type: [Number, String],
  35. default: 0
  36. },
  37. endVal: {
  38. type: [Number, String],
  39. default: 0,
  40. required: true
  41. },
  42. duration: {
  43. type: [Number, String],
  44. default: 2000
  45. },
  46. autoplay: {
  47. type: Boolean,
  48. default: true
  49. },
  50. decimals: {
  51. type: [Number, String],
  52. default: 0
  53. },
  54. useEasing: {
  55. type: Boolean,
  56. default: true
  57. },
  58. decimal: {
  59. type: [Number, String],
  60. default: '.'
  61. },
  62. color: {
  63. type: String,
  64. default: '#303133'
  65. },
  66. fontSize: {
  67. type: [Number, String],
  68. default: 50
  69. },
  70. bold: {
  71. type: Boolean,
  72. default: false
  73. },
  74. separator: {
  75. type: String,
  76. default: ''
  77. }
  78. },
  79. data() {
  80. return {
  81. localStartVal: this.startVal,
  82. displayValue: this.formatNumber(this.startVal),
  83. printVal: null,
  84. paused: false,
  85. localDuration: Number(this.duration),
  86. startTime: null,
  87. timestamp: null,
  88. remaining: null,
  89. rAF: null,
  90. lastTime: 0
  91. };
  92. },
  93. computed: {
  94. countDown() {
  95. return this.startVal > this.endVal;
  96. }
  97. },
  98. watch: {
  99. startVal() {
  100. this.autoplay && this.start();
  101. },
  102. endVal() {
  103. this.autoplay && this.start();
  104. }
  105. },
  106. mounted() {
  107. this.autoplay && this.start();
  108. },
  109. methods: {
  110. easingFn(t, b, c, d) {
  111. return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
  112. },
  113. requestAnimationFrame(callback) {
  114. const currTime = new Date().getTime();
  115. const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
  116. const id = setTimeout(() => {
  117. callback(currTime + timeToCall);
  118. }, timeToCall);
  119. this.lastTime = currTime + timeToCall;
  120. return id;
  121. },
  122. cancelAnimationFrame(id) {
  123. clearTimeout(id);
  124. },
  125. start() {
  126. this.localStartVal = this.startVal;
  127. this.startTime = null;
  128. this.localDuration = this.duration;
  129. this.paused = false;
  130. this.rAF = this.requestAnimationFrame(this.count);
  131. },
  132. reStart() {
  133. if (this.paused) {
  134. this.resume();
  135. this.paused = false;
  136. } else {
  137. this.stop();
  138. this.paused = true;
  139. }
  140. },
  141. stop() {
  142. this.cancelAnimationFrame(this.rAF);
  143. },
  144. resume() {
  145. this.startTime = null;
  146. this.localDuration = this.remaining;
  147. this.localStartVal = this.printVal;
  148. this.requestAnimationFrame(this.count);
  149. },
  150. reset() {
  151. this.startTime = null;
  152. this.cancelAnimationFrame(this.rAF);
  153. this.displayValue = this.formatNumber(this.startVal);
  154. },
  155. count(timestamp) {
  156. if (!this.startTime) this.startTime = timestamp;
  157. this.timestamp = timestamp;
  158. const progress = timestamp - this.startTime;
  159. this.remaining = this.localDuration - progress;
  160. if (this.useEasing) {
  161. if (this.countDown) {
  162. this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
  163. } else {
  164. this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
  165. }
  166. } else {
  167. if (this.countDown) {
  168. this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
  169. } else {
  170. this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
  171. }
  172. }
  173. if (this.countDown) {
  174. this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
  175. } else {
  176. this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
  177. }
  178. this.displayValue = this.formatNumber(this.printVal);
  179. if (progress < this.localDuration) {
  180. this.rAF = this.requestAnimationFrame(this.count);
  181. } else {
  182. this.$emit('end');
  183. }
  184. },
  185. isNumber(val) {
  186. return !isNaN(parseFloat(val));
  187. },
  188. formatNumber(num) {
  189. num = Number(num);
  190. num = num.toFixed(Number(this.decimals));
  191. num += '';
  192. const x = num.split('.');
  193. let x1 = x[0];
  194. const x2 = x.length > 1 ? this.decimal + x[1] : '';
  195. const rgx = /(\d+)(\d{3})/;
  196. if (this.separator && !this.isNumber(this.separator)) {
  197. while (rgx.test(x1)) {
  198. x1 = x1.replace(rgx, '$1' + this.separator + '$2');
  199. }
  200. }
  201. return x1 + x2;
  202. },
  203. destroyed() {
  204. this.cancelAnimationFrame(this.rAF);
  205. }
  206. }
  207. };
  208. </script>
  209. <style lang="scss" scoped>
  210. .u-count-num {
  211. /* #ifndef APP-NVUE */
  212. display: inline-flex;
  213. /* #endif */
  214. text-align: center;
  215. }
  216. </style>