How do I reset a JQueryUI effect animation?

Viewed 152

I have two different timer bars that each winds down over 5 seconds. But when I try to stop and reset the first timer, it won't reset, only continues from where it stopped.

Desired behavior: Timer1 starts User hits key Timer1 stops halfway complete Timer2 starts Timer2 expires Timer1 resets to 100% and starts

This is how the timer1 starts:

function StartTimer1(){       
 $("#timer-bar").show().width("100%");
    
$("#timer-bar").hide({
            effect: "blind",
            easing: "linear",
            duration: 5000,
            direction: "left",
            complete: function () {
                //do stuff
            }
        });
}

Then when someone hits a key, I run

 //Stop timer1
     $("#timer-bar").stop(true, false);

//Start timer2 
    $("#answer-timer-bar").hide({
        effect: "blind",
        easing: "linear",
        duration: 5000,
        direction: "left",
        complete: function () {
            //do stuff
        }
    });

Then after timer2 expires, I hit another key which calls StartTimer1() again. And even though I reset the width to 100% before I start the animation, it just continues from wherever it first stopped. I read about queues and thought that was my answer, but I've tried adding unique queue names to each of the timer options, but whenever I do that the timer never starts for some reason. Same when I just set queue to true. I've tried the different options for clearQueue and jumpToEnd parameters when calling Stop(), but can't seem to get the behavior I want.

Any help would be appreciated. Thanks.

JSFiddle: https://jsfiddle.net/58rzg6w0/7

4 Answers

Animation over hide and show methods?
According to my research and this stackoverflow post we would have more control over the animation if we use $(selector).animate() instead of $(selector).hide() and $(selector).show().

Use of container elements for the animation
I have separated the css-styles and added overlays for the animation itself to prevent the container from collapsing (the element would have no height if it is display: none). This can be useful or not, it depends on your aim. You can still hide them anyway so there is no drawback in using this method. It might be useful to separate the animation from the container.

Timing issue on resuming animation
If one of the timers happens to be stopped and resumed during the animation, the animation will still have the same duration for 'less distance' resulting in a slower animation.

However, If we reset the animation of the timer1, as you did in your example, then this is not a problem, but otherwise, we have to adapt the animation duration relative to the animation progress and the total animation length (you can see this in the script below).

Just for fun - display the time
Normally, a timer displays the time, therefore I have added the duration of the animation onto the overlay. This is just a simple addition that you may want to add (plus styling). The duration of the animation can be set on the timer element via data-duration (in ms).

$('document').ready(function() {
  ResetAll();
});

function TimeLeft(id, initialTime) {
  const $timer = $('#' + id);
  const $overlay = $timer.find('.overlay');
  const percentageToGo = (parseInt($overlay.css('width')) / parseInt($timer.css('width')));
  return percentageToGo * initialTime;
}

function StartTimer(id, onComplete = null) {
  const $timer = $('#' + id);
  const $overlay = $timer.find('.overlay');
  const duration = $timer.data('duration');
  const time = TimeLeft(id, duration);
  $overlay.animate({
    width: '0%'
  }, {
    duration: time,
    easing: 'linear',
    step: function() {
      $overlay.html(Math.round(TimeLeft(id, duration)) / 1000);
    },
    complete: function() {
      // $timer.css('display', 'none'); // remove all comments to hide the timer element completly
      $overlay.html('');
      if (onComplete && typeof onComplete === 'function') {
        onComplete();
      }
    }
  });
}

function StopTimer(id) {
  $('#' + id + ' .overlay').stop(true, false);
}

function ResetTimer(id) {
  const $timer = $('#' + id);
  const $overlay = $timer.find('.overlay');
  $overlay.stop(true).css('width', '100%');
  // $timer.css('display', 'block'); // remove all comments to hide the timer element completly
  $overlay.html(Math.round(TimeLeft(id, $timer.data('duration'))) / 1000);
}

function StopTimer1AndStartTimer2() {
  ResetTimer('timer1');
  ResetTimer('timer2');
  StartTimer('timer2', function() {
    // $('#timer2').css('display', 'none'); // remove all comments to hide the timer element completly
    StartTimer('timer1');
  });
}

function ResetAll() {
  ResetTimer('timer1');
  ResetTimer('timer2');
  // $('.timer').css('display', 'block'); // remove all comments to hide the timer element completly
}
.timer {
  position: relative;
  height: 30px;
  width: 100%;
  z-index: 1;
}

.overlay {
  color: white;
  position: absolute;
  width: 100%;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: -1;
}

#timer1 .overlay {
  background-color: red;
}

#timer2 .overlay {
  background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<div id="timer1" data-duration="5000" class="timer">
  <div class="overlay"></div>
</div>
<div id="timer2" data-duration="3000" class="timer">
  <div class="overlay"></div>
</div>
<p>This first button is probably what you want, the rest is extra:</p>
<button onclick="StopTimer1AndStartTimer2();">Stop Timer1 - Reset Timer1 - Start Timer2 - on complete Start Timer2</button>
<button onclick="StartTimer('timer1');">Start/Resume Timer1</button>
<button onclick="StartTimer('timer2');">Start/Resume Timer2</button>
<button onclick="StartTimer('timer1'); StartTimer('timer2');">Start/Resume All</button>
<button onclick="StopTimer('timer1');">Stop Timer1</button>
<button onclick="StopTimer('timer2');">Stop Timer2</button>
<button onclick="StopTimer('timer1'); StopTimer('timer2');">Stop All</button>
<button onclick="ResetTimer('timer1');">Reset Timer1</button>
<button onclick="ResetTimer('timer2');">Reset Timer2</button>
<button onclick="ResetAll();">Reset All</button>

To reset animation:

// Remove style
$("#timer-bar").removeAttr('style');
// Set initial width
$("#timer-bar").show().width("100%");

or:

$('#timer-bar').stop(true).css('width', '100%');

Use .stop(true, true) to get rind of placeholder (.stop( [clearQueue ] [, jumpToEnd ] )). jumpToEnd will hide it, so just show it again, and call it back after timer2 ends:

 function StartTimer1() {
  $("#timer1").hide({
    effect: "blind",
    easing: "linear",
    duration: 5000,
    direction: "left",
    complete: function() {}
  });
}

function StopTimer1() {
  $("#timer1").stop(true, true).show();

  $("#timer2").hide({
    effect: "blind",
    easing: "linear",
    duration: 2000,
    direction: "left",
    complete: function() {
      StartTimer1()
    }
  });
}

Example with reset red bar soon as button is hit:

function StartTimer1() {
  $("#timer1").hide({
    effect: "blind",
    easing: "linear",
    duration: 5000,
    direction: "left",
    complete: function() {}
  });
}

function StopTimer1() {
  $("#timer1").stop(true, true).show();

  $("#timer2").hide({
    effect: "blind",
    easing: "linear",
    duration: 2000,
    direction: "left",
    complete: function() {
      StartTimer1()
    }
  });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id="timer1" style="height:30px; width:100%; background-color:red">

</div>


<div id="timer2" style="height:30px; width:100%; background-color:blue">

</div>

Click First button, then click second, then click first again
<button onclick="StartTimer1();">
Start Timer1
</button>


<button onclick="StopTimer1();">
Stop Timer1 and Start Timer2
</button>

And if you want to reset red bar after blue is done animating:

Use .stop(true, false); and reset style (.attr("style")when blue bar is done.

function StartTimer1() {
  $("#timer1").hide({
    effect: "blind",
    easing: "linear",
    duration: 5000,
    direction: "left",
    complete: function() {}
  });
}

function StopTimer1() {
  $("#timer1").stop(true, false);

  $("#timer2").hide({
    effect: "blind",
    easing: "linear",
    duration: 2000,
    direction: "left",
    complete: function() {
      $("#timer1").attr("style", "height:30px; width:100%; background-color:red");
      StartTimer1();
    }
  });
}

function StartTimer1() {
  $("#timer1").hide({
    effect: "blind",
    easing: "linear",
    duration: 5000,
    direction: "left",
    complete: function() {}
  });
}

function StopTimer1() {
  $("#timer1").stop(true, false);

  $("#timer2").hide({
    effect: "blind",
    easing: "linear",
    duration: 2000,
    direction: "left",
    complete: function() {
      $("#timer1").attr("style", "height:30px; width:100%; background-color:red");
      StartTimer1();
    }
  });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id="timer1" style="height:30px; width:100%; background-color:red">

</div>


<div id="timer2" style="height:30px; width:100%; background-color:blue">

</div>

Click First button, then click second, then click first again
<button onclick="StartTimer1();">
Start Timer1
</button>


<button onclick="StopTimer1();">
Stop Timer1 and Start Timer2
</button>

You can .clone() timer1 at the beginning and reset it to its initial value with .replaceWith() when timer2 effect ends:

var initialValue = null;
function StartTimer1() {
    if (initialValue == null) {
        initialValue = $("#timer1").clone();
    } else {
        $("#timer1").replaceWith(initialValue);
    }
    $("#timer1").show().width("100%");

    $("#timer1").hide({
        effect: "blind",
        easing: "linear",
        duration: 5000,
        direction: "left",
        complete: function () {
        }
    });
}

function StopTimer1(){
    $("#timer1").stop(true, false);

    $("#timer2").hide({
        effect: "blind",
        easing: "linear",
        duration: 2000,
        direction: "left",
        complete: function () {
            StartTimer1();
        }
    });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<div id="timer1" style="height:30px; width:100%; background-color:red"></div>
<div id="timer2" style="height:30px; width:100%; background-color:blue"></div>
Click First button, then click second, then click first again
<button onclick="StartTimer1();">Start Timer1</button><button onclick="StopTimer1();">Stop Timer1 and Start Timer2</button>

Related