NDi
NDi

Reputation: 342

Javascript counter up with various setTimeout

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

Answers (3)

Mr. Polywhirl
Mr. Polywhirl

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

Elvis
Elvis

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

mrblue
mrblue

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

Related Questions