Search code examples
javascriptcountdown

Countdown bar Javascript not decreasing


I have this JS code for a countdown progress bar that should take a time value and decrease until time is reached, then display EXPIRED.

function progress(timeleft, timetotal, $element) {
  var bar = document.getElementById("#progressBar")
  var progressBarWidth = (timeleft * bar.width()) / timetotal
  console.log("width is" + bar.width() + "time left is" + timeleft)
  $element.find("div").animate({
    width: progressBarWidth
  }, timeleft == timetotal ? 0 : 1000, "linear")

  if (timeleft > 0) {
    setTimeout(function() {
      progress(timeleft - 1, timetotal, $element)
    }, 1000)
  }
}

progress(180, 180, $("#progressBar"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="progressBar">
  <div></div>
</div>

Problem is that here I set it to 3min for testing and bar doesn't decrease. I've debugged via console and 'bar.width()' seems to be undefined. Any ideas how to fix it? Thanks!


Solution

  • You are already passing in the $element, which IS bar.

    function progress(timeleft, timetotal, $element) {
      var progressBarWidth = (timeleft * $element.width()) / timetotal
      console.log(`width: ${$element.width()} px  |  time left: ${timeleft} sec`)
      $element.find("div").animate({
        width: progressBarWidth
      }, timeleft == timetotal ? 0 : 1000, "linear")
    
      if (timeleft > 0) {
        setTimeout(progress, 1000, timeleft - 1, timetotal, $element)
      }
    }
    
    progress(60, 60, $("#progressBar"))
    #progressBar div {
      background: green;
      height: 1em;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="progressBar">
      <div></div>
    </div>

    Note: You can invoke setTimeout without creating a nested function call. The parameters following the timeout (2nd param) will be passed into the callback function.

    Replace this:

    if (timeleft > 0) {
      setTimeout(function() {
        progress(timeleft - 1, timetotal, $element)
      }, 1000)
    }
    

    With this:

    if (timeleft > 0) {
      setTimeout(progress, 1000, timeleft - 1, timetotal, $element)
    }
    

    jQuery plugin!

    Here is a jQuery plugin version

    (($) => {
      const init = ($bar) => {
        if ($bar.find('div').length === 0) $bar.append($('<div>'));
      }
      const run = ($bar, duration, timeRemaining, callback) => {
        update($bar, duration, timeRemaining)
        if (timeRemaining > 0) {
          setTimeout(tick, 1000, $bar, duration, timeRemaining, callback)
        } else {
          callback()
        }
      }
      const update = ($bar, duration, timeRemaining) => {
        const width = (timeRemaining * $bar.width()) / duration
        $bar.find('div').animate({
          width: width
        }, timeRemaining == duration ? 0 : 1000, 'linear')
      }
      const tick = ($bar, duration, timeRemaining, callback) => {
        run($bar, duration, timeRemaining - 1, callback)
      }
      $.fn.progress = function(duration, timeRemaining, callback) {
        init(this)
        run(this, duration, timeRemaining, callback);
        return this
      }
    })(jQuery);
    
    $('#progress-bar-1').progress(10, 10, () => {
      console.log('Task #1 completed!')
    })
    
    $('#progress-bar-2').progress(5, 5, () => {
      console.log('Task #2 completed!')
    })
    div[id^="progress-bar"] div {
      background: green;
      height: 1em;
      margin-bottom: 0.5em;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="progress-bar-1"></div>
    <div id="progress-bar-2"></div>