Reputation: 342
I created from various sources the simplest js counter up I could find. How can I make different setTimeout for each counter so both of them end at the same time?
UPDATE: I have minimized the right answer below and this is the final code:
const counters = document.querySelectorAll('.counter');
const duration = 2000; // Finish in 2 seconds
const speed = 2000;
const state = {};
const max = Math.max(...[...counters]
.map(counter => +counter.dataset.target));
const tick = duration / max;
const updateCount = (counter) => {
const target = +counter.dataset.target;
const value = +counter.dataset.value;
const ratio = target / max;
const ms = Math.ceil(ratio * tick);
const incr = target / speed;
const newVal = value + incr;
if (value < target) {
counter.innerText = Math.floor(newVal);
counter.dataset.value = newVal;
setTimeout(updateCount, ms, counter);
} else {
counter.innerText = target;
counter.dataset.value = target;
}
};
counters.forEach(updateCount);
<div class="counter" data-key="1" data-value="0" data-target="1000">0</div>
<div class="counter" data-key="2" data-value="0" data-target="2000">0</div>
Upvotes: 0
Views: 358
Reputation: 48640
Each timer has to run at an increases rate relative to the max time.
This is a rudimentary example, and it still may need some work. I also recmomment using the dataset
property to store the value and only re-render the value (in case the result is a floating-point number).
const results = document.querySelector('.results');
const counters = document.querySelectorAll('.counter');
const duration = 2000; // Finish in 2 seconds
const speed = 1000;
const state = {};
const max = Math.max(...[...counters]
.map(counter => +counter.dataset.target));
const tick = duration / max;
const updateCount = (counter) => {
const target = +counter.dataset.target;
const value = +counter.dataset.value;
const ratio = target / max;
const ms = Math.ceil(ratio * tick);
const incr = target / speed;
const newVal = value + incr;
const { dataset: { key }} = counter;
state[key] = {
...state[key],
ratio, ms, incr, value
};
results.textContent = JSON.stringify(state, null, 2);
if (value < target) {
counter.innerText = Math.floor(newVal);
counter.dataset.value = newVal;
setTimeout(updateCount, ms, counter);
} else {
counter.innerText = target;
counter.dataset.value = target;
}
};
counters.forEach(updateCount);
.results {
border: thin solid grey;
padding: 0.25em;
white-space: pre;
font-family: monospace;
}
<div class="counter" data-key="1" data-value="0" data-target="1000">0</div>
<div class="counter" data-key="2" data-value="0" data-target="2000">0</div>
<div class="counter" data-key="3" data-value="0" data-target="4000">0</div>
<div class="results"></div>
Upvotes: 1
Reputation: 75
Try getting the avg, like this:
const counters = document.querySelectorAll('.counter');
const speed = 1;
let avg=0;
counters.forEach(counter => {
avg+=parseInt(counter.getAttribute('data-target'))
});
avg=avg/counters.length;
counters.forEach(counter => {
const updateCount = () => {
const count = +counter.innerText;
const target = +counter.getAttribute('data-target')
const inc = counter.getAttribute('data-target') / (speed * avg);
if (count < target) {
counter.innerText = count + inc;
setTimeout(updateCount, 100);
} else {
counter.innerText = target;
}
};
updateCount();
});
<div class="counter" data-target="150">0</div>
<div class="counter" data-target="300">0</div>
Upvotes: 1
Reputation: 237
It works like this because your setTimeout(updateCount, 100);
runs after 100 miliseconds. So when you are counting from 0 to 150 and you are adding a number once per 100ms it will be twice faster as counting to 300.
You can make it end at the same time, when you change setTimeout()
to run after 50 miliseconds for counting to 300
Something like this
setTimeout(updateCount150, 100);
setTimeout(updateCount300, 50);
Of course you need to adjust those both functions accordingly.
Upvotes: 1