Reputation: 29
I am currently working on a web project that has some incrementation animation and I've created a counter function like this :
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.innerText;
const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
if (count < target) {
counter.innerText = count + speed;
setTimeout(updateCount,1);
} else {
counter.innerText = target;
}
}
updateCount();
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html , body {
width: 100%;height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
/* HEADER */
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" class="counter">0</span>
<span data-target="50" class="counter">0</span>
<span data-target="250000" class="counter">0</span>
The problem is the number display in the web is decimal. I've tried math.ceil
,math.floor
, ... But they seem to mess up the speed
of the counter . Is there a way of display only the integer part WITHOUT
messing with the speed
?
Upvotes: 1
Views: 120
Reputation: 123397
You need to pass the incremented value to the function itself instead of reading the printed value.
See the comments on the code.
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
/* expecting a parameter */
const updateCount = (count) => {
const target = +counter.getAttribute('data-target');
/* at the first function call without argument
* read the innerText value, otherwise read it from
* the argument (change const with var)
*/
var count = count || +counter.innerText;
const speed = target / 1000;
if (count < target) {
/* print the value with parseInt() so you keep
* the integer part only
*/
counter.innerText = parseInt(count + speed, 10);
/* call the function passing the original sum of
* count + speed
*/
setTimeout(function() {
updateCount(count+speed)
}, 1);
}
else {
/* use parseInt() */
counter.innerText = parseInt(target, 10);
}
}
updateCount();
});
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
display: block;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" class="counter">0</span>
<span data-target="50" class="counter">0</span>
<span data-target="250000" class="counter">0</span>
As a side note, you can also pass a second target
argument to the function, instead of continuously access the DOM to read it, exactly as I've done for count
, since it doesn't change.
Upvotes: 1
Reputation: 106038
This might help you : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed
example with an extra data-attribute:
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.getAttribute('data-count');
const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
if (count < target) {
counter.setAttribute('data-count', count + speed);
let stripped = (count + speed).toFixed(0);
counter.innerText = stripped
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
}
updateCount();
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
/* HEADER */
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" data-count="0" class="counter">0</span>
<span data-target="50" data-count="0" class="counter">0</span>
<span data-target="250000" data-count="0" class="counter">0</span>
Upvotes: 0
Reputation: 507
The same value is being used for the calculation and displaying the value so if you round it off you lose the precision. If on the first iteration it does 1.3+3=4.3 on the next iteration the 4.3 will be rounded up and the sum would be 5+3. That's what's making the speed seem off.
Adding another hidden attribute to the span (count-value
in the code below) gives you a way to store the whole number with all the decimals to use in the calculations and then you can display the number without decimals to the users without it throwing off the sum.
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
const updateCount = () => {
const target =+counter.getAttribute('data-target');
//update count with the full value
const count =+counter.getAttribute('count-value');
const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
if (count < target) {
//set the innertext to the rounded up value
counter.innerText = Math.ceil(count + speed);
//update the hidden attribute with the full value
counter.setAttribute('count-value', count + speed);
setTimeout(updateCount,1);
} else {
counter.innerText = target;
}
}
updateCount();
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html , body {
width: 100%;height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
/* HEADER */
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
}
<span data-target="20" count-value="0" class="counter">0</span>
<span data-target="100" count-value="0" class="counter">0</span>
<span data-target="50" count-value="0" class="counter">0</span>
<span data-target="250000" count-value="0" class="counter">0</span>
Upvotes: 0