I improved [@AnandShiva's][1] Script it appears there were 3-4 edge cases which needed proper handling:
The component properly handles decimal places based on the new
value (Previously when transitioning from 0.33 to 0.3344 there could
appear 0.033xxxxxxxxxxxx numbers now it is limited to 0.33xx - The
new number digit count determines maximum allowed digits after zero)
There is a need to disable animations in some cases so
animate=true/false added
Zero case was not handled, the component now ensures proper handling of
zero
Sometimes APIs provide string inputs also instead of floats or
integers so component now also accepts strings
Usage:
<template>
<div>
<animated-number :value="currentNumber" :animate="true"></animated-number>
</div>
</template>
<script>
import AnimatedNumber from './AnimatedNumber.vue';
export default {
components: {
AnimatedNumber
},
data() {
return {
currentNumber: 123.45
};
},
mounted() {
// Example of changing the number after 2 seconds
setTimeout(() => {
this.currentNumber = 678.90;
}, 2000);
}
};
</script>
And the component itself:
<template>
<span>{{ formattedNumber }}</span>
</template>
<script>
export default {
data() {
return {
displayNumber: 0,
counter: null,
maxDecimalPlaces: 0
};
},
props: {
value: {
type: [Number, String],
default: 0
},
animate: {
type: Boolean,
default: true
}
},
watch: {
value() {
const newValue = parseFloat(this.value) || 0; // Parse the value to a number, default to 0 if parsing fails
this.maxDecimalPlaces = this.getDecimalPlaces(newValue);
// console.log('maxDecimalPlaces', this.maxDecimalPlaces);
// console.log('Number prop changed:', newValue);
if (!this.animate) {
this.displayNumber = newValue;
return;
}
clearInterval(this.counter);
if (newValue === this.displayNumber) {
return;
}
this.counter = setInterval(() => {
let change = (newValue - this.displayNumber) / 5;
this.displayNumber += change;
if (Math.abs(newValue - this.displayNumber) < Math.pow(10, -this.maxDecimalPlaces)) {
this.displayNumber = newValue;
clearInterval(this.counter);
}
}, 20);
}
},
computed: {
formattedNumber() {
return this.formatNumber(this.displayNumber);
}
},
methods: {
getDecimalPlaces(number) {
const parts = number.toString().split('.');
return parts.length > 1 ? parts[1].length : 0;
},
formatNumber(number) {
if (number === 0) return '0'; // Explicitly handle the case where the number is 0
return number.toFixed(this.maxDecimalPlaces).replace(/\.?0+$/, '');
}
},
mounted() {
const initialValue = parseFloat(this.value) || 0; // Parse the initial value to a number, default to 0 if parsing fails
this.maxDecimalPlaces = this.getDecimalPlaces(initialValue);
this.displayNumber = initialValue;
// console.log('Component mounted, initial displayNumber:', this.displayNumber);
}
};
</script>
v-animate. I'll look at it more tomorrow