Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating a queue of elements are skipping to final element

CodePen

Rules:

  • Every click will add a <progress> element.
  • Each progress bar will animate to full.
  • Animations wont start until previous has finished.

Problem:

If you click rapidly it will only animate the last progress bar. And animations should happen one by one until completion.

Question:

When clicking rapidly, why are the animations out of order?

HTML:

<div class="container">
  <div class="add-progress-container">
    <input class="input-seconds" type="number" min="1" max="10" value="1">
    <button class="add-progress">Add progress</button>
  </div>  
  <div class="progress-container"></div>
</div>

JS:

const container = document.querySelector('.progress-container');
const inputSeconds = document.querySelector('.input-seconds');
const addBtn = document.querySelector('.add-progress');
let animating = false;


function animateProgress(duration, el) {
  const intervalId = setInterval(() => {
    if(el.value >= el.max) {
      animating = false;
      window.clearInterval(intervalId);
      console.log('finished');
      checkQueue();
      return;
    }

    el.value += el.max/duration;
  }, 1000);
} 


function getSeconds() {
  return parseInt(inputSeconds.value, 10);
}

let progressCount = 0;

function createProgress() {
  const template = `<progress data-progress="${progressCount}" value="0" max="100"></progress>`;
  container.innerHTML += template;
  const el = document.querySelector(`[data-progress="${progressCount}"]`);
  progressCount++;
  return el;
}

let queue = [];
function addProgress() {
  const el = createProgress()
  queue.push(el);
  checkQueue();
}

function checkQueue() {
  if(queue.length && !animating) {
    animating = true;
    animateProgress(getSeconds(), queue.shift());
  }  
}

addBtn.addEventListener('click', addProgress);
like image 877
Armeen Harwood Avatar asked Dec 09 '25 19:12

Armeen Harwood


1 Answers

The innerHTML+= assignment will recreate all previous elements within the container element, meaning that your references to other progress elements are no longer to the actual elements that this assignment creates.

You should add a new progress element like this:

function createProgress() {
  const el = document.createElement("progress");
  el.setAttribute("data-progress", progressCount);
  el.setAttribute("value", 0);
  el.setAttribute("max", 100);
  container.appendChild(el);
  return el;
}

As Makyuu commented below, the number of steps for the individual progress elements are only taken when their animations start. This means that if you change the input value, it will apply also to previously created elements (when they have not started animating yet).

If it is intended to use the number of steps as they were indicated at the moment of element creation, then modify two lines in your code:

queue.push([getSeconds(), el]);

and:

animateProgress(...queue.shift());
like image 120
trincot Avatar answered Dec 11 '25 08:12

trincot