1 | <script>
|
2 | export default {
|
3 | name: 'AnimatedNumber',
|
4 | props: {
|
5 | number: {
|
6 | type: Number,
|
7 | required: true,
|
8 | },
|
9 | /**
|
10 | * Controls how long it takes for the animation to complete.
|
11 | */
|
12 | duration: {
|
13 | type: Number,
|
14 | required: false,
|
15 | default: 2000,
|
16 | },
|
17 | /**
|
18 | * Controls the number of decimal places displayed in the output.
|
19 | */
|
20 | decimalPlaces: {
|
21 | type: Number,
|
22 | required: false,
|
23 | default: 0,
|
24 | },
|
25 | animateOnMount: {
|
26 | type: Boolean,
|
27 | required: false,
|
28 | default: false,
|
29 | },
|
30 | },
|
31 | data() {
|
32 | return {
|
33 | displayNumber: 0,
|
34 | startTime: null,
|
35 | };
|
36 | },
|
37 | computed: {
|
38 | animatedNumber() {
|
39 | return this.displayNumber.toFixed(this.decimalPlaces);
|
40 | },
|
41 | },
|
42 | ready() {
|
43 | this.displayNumber = this.number ? this.number : 0;
|
44 | },
|
45 | watch: {
|
46 | number() {
|
47 | this.animate();
|
48 | },
|
49 | },
|
50 | mounted() {
|
51 | if (this.animateOnMount) {
|
52 | this.animate();
|
53 | } else {
|
54 | this.displayNumber = this.number;
|
55 | }
|
56 | },
|
57 | methods: {
|
58 | animate() {
|
59 | this.$emit('animating');
|
60 | window.requestAnimationFrame(this.count);
|
61 | },
|
62 | count(timestamp) {
|
63 | if (!this.startTime) {
|
64 | this.startTime = timestamp;
|
65 | }
|
66 |
|
67 | const progress = timestamp - this.startTime;
|
68 |
|
69 | if (progress < this.duration) {
|
70 | if (this.displayNumber !== this.number) {
|
71 | const change = (this.number - this.displayNumber) / (this.duration / 100);
|
72 | this.displayNumber += change;
|
73 | }
|
74 | window.requestAnimationFrame(this.count);
|
75 | } else {
|
76 | this.displayNumber = this.number;
|
77 | this.startTime = null;
|
78 | this.$emit('animated');
|
79 | }
|
80 | },
|
81 | },
|
82 | };
|
83 | </script>
|
84 | <template>
|
85 | <span>{{ animatedNumber }}</span>
|
86 | </template>
|