'Progress bar with intervals

I am trying to create a progress bar with steps and a play/pause button.

I am having trouble with creating the play/pause button but the case is that onload progress bar should not be filling, only after pressing the play button.

The second case I can't figure out is when the progress bar is running if the pause button is pressed it should stop at the end of the interval in which he currently is.

For example, if we press play and the progress bar starts filling up if the pause button is pressed between the starting point and 1st interval, it should stop at the end of the 1st interval, if the pause button is pressed between 1st interval and 2nd interval, it should continue to fill to the end of the 2nd interval etc.

I hope I made it clear.

const checkpoints = [
  {
    perc: 25, 
    label: "25%\n<b>2018</b>"
  },
  {
    perc: 35, 
    label: "35%\n<b>2019</b>"
  },
  {
    perc: 45,
    label: "45%\n<b>2020</b>"
  },
  {
    perc: 55,
    label: "55%\n<b>2021</b>"
  },
  {
    perc: 100,
    label: "100%\n<b>2022</b>"
  }
];
const step = 1;
const timeInterval = 100;
const playLabel = 'PLAY';
const pauseLabel = 'PAUSE';

//on document ready..
$(document).ready(()=>{
  
  //shows checkpoints under the progressbar
  for(checkpoint of checkpoints){
    $('#rulers')
      .append(`<p class='ruler' style='width:${checkpoint.perc}%;'>${checkpoint.label}</p>`);
  }
        
  //attaches click event handler to #playpause button
  $('#playpause').on('click', (event)=>{ toggleButton( $(event.currentTarget) ) });
});

function toggleButton(playPauseButton){
  const status = $(playPauseButton).attr('data-status');  
  //if the button was in pause state (and having the Play label)
  if (status == "pausing"){        
    $(playPauseButton)        
      //toggles it to status 'playing'
      .attr('data-status', 'playing')
      //toggles it to "Pause" label
      .text(pauseLabel);    
    //begins incrementing the progressbar
    const intervals = checkpoints.map(cp => cp.perc);
    setProgressWithCheckpoints(intervals, step, timeInterval);
  }
  //if the button was in playing state (and having the Pause label)
  else if(status == 'playing'){
    $(playPauseButton)
      //toggles it to status 'pausing'
      .attr('data-status', 'pausing')
      //toggles it to "Play" label
      .text(playLabel);
    //if the progressbar is still running to complete its task,
    if( isStillRunning() === true ){
      //sets the playpause button as disabled so that you can't click it
      $(playPauseButton).prop('disabled', true);
    }
  }
}

//set the progressbar at perc
function setProgress(perc){    
  $(".progress .progress-bar")
    .css('width', `${perc}%`)
    .attr('aria-valuenow', perc)
  $(".progress .progress-bar span")
    .text(`${perc}%`);    
}

//get progressbar %
function getProgress(){
  const valueNow = $(".progress .progress-bar")
                    .attr('aria-valuenow');
  return parseInt(valueNow);
}

//return true/false if the progress is still running
function isStillRunning(){
  if ( $('#playpause').attr('data-stillrunning') == 'yes' )
    return true;
  return false;
}

//sets the state stillrunning to yes
function setStillRunning(){
  $('#playpause').attr('data-stillrunning', 'yes');
}

//sets the state stillrunning to no and enables the button (in case it was disabled)
function setNotStillRunning(){
  $('#playpause')
    .attr('data-stillrunning', 'no')
    .prop('disabled', false);
    
   if ( $('#playpause').attr('data-status') == 'playing' )
    toggleButton(  $('#playpause') );
}
.progress{  
  margin: 50px 20px 20px 0;
}

.progress{  
  display: flex;
  width: 100%;  
  height: 2rem;
  overflow: hidden;
  border: solid 1px lightgray;
  width: 100%;
}

.progress-bar{
  color: white;
  vertical-align: middle;
  font-weight: 600;  
  padding-top: 6px;
}

.progress-bar span{
  padding-left: 10px;
}

#rulers {
  position: relative;
  height: 3em;
  border-bottom: solid 1px lightgray;
}

#rulers .ruler{
  position: absolute;
  text-align: right;
  margin-top: 0;
  white-space: pre-line;
  border-right: solid 2px darkgray;
  padding-right: 4px;
  box-sizing: border-box;
}

#playpause {
  display: block;
  cursor: pointer;
  margin-top: 20px;
  padding: 2px 10px;
  font-size: 18px;
}

#playpause:disabled{
  cursor: not-allowed;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="progress">
  <div
    class="progress-bar"
    role="progressbar"
    aria-valuenow="0"
    aria-valuemin="0"
    aria-valuemax="100"
    style="width: 0%;background: green;">
    <span></span>
  </div>
</div>

<div id="rulers">
</div>

<button id="playpause" data-status="pausing">PLAY</button>

Here is the link to the codepen: https://codepen.io/Wildfactor/pen/WNMwbEP



Solution 1:[1]

This is an implementation of a progress bar having some defined checkpoints.

The defined checkpoints hold the percentage value of each one and its label to show in the ruler.

After pressing the play button, the button label toggles to "pause" and the progress bar will begin advancing. If the pause button gets clicked, the progress will advance until next checkpoint and stop.

If the progress arrives to 100% and the pause button wasn't still pressed, the progress will stop and the button will revert to pause state.

When the progress is running until next checkpoint and the button was pressed to pause, it won't be possible to press the play button until the progressbar will reach the checkpoint.

const checkpoints = [
  {
    perc: 25, 
    label: "25%\n<b>2018</b>"
  },
  {
    perc: 35, 
    label: "35%\n<b>2019</b>"
  },
  {
    perc: 45,
    label: "45%\n<b>2020</b>"
  },
  {
    perc: 55,
    label: "55%\n<b>2021</b>"
  },
  {
    perc: 100,
    label: "100%\n<b>2022</b>"
  }
];
const step = 1;
const timeInterval = 100;
const playLabel = 'PLAY';
const pauseLabel = 'PAUSE';

//on document ready..
$(document).ready(()=>{
  
  //shows checkpoints under the progressbar
  for(checkpoint of checkpoints){
    $('#rulers')
      .append(`<p class='ruler' style='width:${checkpoint.perc}%;'>${checkpoint.label}</p>`);
  }
        
  //attaches click event handler to #playpause button
  $('#playpause').on('click', (event)=>{ toggleButton( $(event.currentTarget) ) });
});

function toggleButton(playPauseButton){
  const status = $(playPauseButton).attr('data-status');  
  //if the button was in pause state (and having the Play label)
  if (status == "pausing"){        
    $(playPauseButton)        
      //toggles it to status 'playing'
      .attr('data-status', 'playing')
      //toggles it to "Pause" label
      .text(pauseLabel);    
    //begins incrementing the progressbar
    const intervals = checkpoints.map(cp => cp.perc);
    setProgressWithCheckpoints(intervals, step, timeInterval);
  }
  //if the button was in playing state (and having the Pause label)
  else if(status == 'playing'){
    $(playPauseButton)
      //toggles it to status 'pausing'
      .attr('data-status', 'pausing')
      //toggles it to "Play" label
      .text(playLabel);
    //if the progressbar is still running to complete its task,
    if( isStillRunning() === true ){
      //sets the playpause button as disabled so that you can't click it
      $(playPauseButton).prop('disabled', true);
    }
  }
}

//set the progressbar at perc
function setProgress(perc){    
  $(".progress .progress-bar")
    .css('width', `${perc}%`)
    .attr('aria-valuenow', perc)
  $(".progress .progress-bar span")
    .text(`${perc}%`);    
}

//get progressbar %
function getProgress(){
  const valueNow = $(".progress .progress-bar")
                    .attr('aria-valuenow');
  return parseInt(valueNow);
}

//return true/false if the progress is still running
function isStillRunning(){
  if ( $('#playpause').attr('data-stillrunning') == 'yes' )
    return true;
  return false;
}

//sets the state stillrunning to yes
function setStillRunning(){
  $('#playpause').attr('data-stillrunning', 'yes');
}

//sets the state stillrunning to no and enables the button (in case it was disabled)
function setNotStillRunning(){
  $('#playpause')
    .attr('data-stillrunning', 'no')
    .prop('disabled', false);
    
   if ( $('#playpause').attr('data-status') == 'playing' )
    toggleButton(  $('#playpause') );
}

function setProgressWithCheckpoints(intervals, step, ms){
  
   setStillRunning();
  
  const valueNow = getProgress();     
  const nextIntervals =
    intervals.filter((interval) => interval > valueNow);      
  const nextInterval =
    (nextIntervals.length > 0) ? nextIntervals[0] : null;

  const newValue = Math.min(nextInterval, valueNow+step);    
  setProgress(newValue);

  const playpauseStatus = $('#playpause').attr('data-status');

  //if progress got to 100% OR
  //   nextInterval got reached while the play button is in pause state
  if(newValue >= 100 || (nextInterval == newValue && playpauseStatus == 'pausing')){
     //ends the calling and set the button as notStillRunning
     setNotStillRunning();
  }
  //else
  else
    //call again the progress function
    setTimeout(()=>{setProgressWithCheckpoints(intervals, step, ms)}, ms);  
}
.progress{  
  margin: 50px 20px 20px 0;
}

.progress{  
  display: flex;
  width: 100%;  
  height: 2rem;
  overflow: hidden;
  border: solid 1px lightgray;
  width: 100%;
}

.progress-bar{
  color: white;
  vertical-align: middle;
  font-weight: 600;  
  padding-top: 6px;
}

.progress-bar span{
  padding-left: 10px;
}

#rulers {
  position: relative;
  height: 3em;
  border-bottom: solid 1px lightgray;
}

#rulers .ruler{
  position: absolute;
  text-align: right;
  margin-top: 0;
  white-space: pre-line;
  border-right: solid 2px darkgray;
  padding-right: 4px;
  box-sizing: border-box;
}

#playpause {
  display: block;
  cursor: pointer;
  margin-top: 20px;
  padding: 2px 10px;
  font-size: 18px;
}

#playpause:disabled{
  cursor: not-allowed;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="progress">
  <div
    class="progress-bar"
    role="progressbar"
    aria-valuenow="0"
    aria-valuemin="0"
    aria-valuemax="100"
    style="width: 0%;background: green;">
    <span></span>
  </div>
</div>

<div id="rulers">
</div>

<button id="playpause" data-status="pausing">PLAY</button>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1