0

I want to make a bar (#innerBar) to decrease 1% in width per second.

The loop doesn't seem to work. My bar drops from 100% to 0% in the blink of an eye.

function timer(){

    var timer;

    for(i=100;i>=0;i--){

        timer = i.toString() + "%";

        setTimeout(function({$('#innerBar').css("width", timer)}, ((100-i)*1000));

    }
}

Note : #innerBar is a DIV with a css property (height:10px). ** + the width from timer(); **

user2864740
  • 60,010
  • 15
  • 145
  • 220
Bird
  • 572
  • 5
  • 15
  • `setTimeOut` make it closure and will work.Here is example http://brackets.clementng.me/post/24150213014/example-of-a-javascript-closure-settimeout-inside – Arpit Srivastava Aug 05 '15 at 04:14
  • 2
    If you're using jQuery, why not use `animate()` – Tushar Aug 05 '15 at 04:15
  • Also, syntax error in the `setTimeout` – Tushar Aug 05 '15 at 04:21
  • possible duplicate of [JavaScript closure inside loops – simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – user2864740 Aug 05 '15 at 04:21
  • @user2864740 Pardon my conviction, but I don't understand how this could possibly be considered a duplicate of that. The questions are *maybe* sort of related. – Maximillian Laumeister Aug 05 '15 at 04:23
  • @MaximillianLaumeister The problem posed by this code - "in the blink of an eye" is caused by the closure usage (over the *same* `timer` variable) and is entirely related. Naturally this causes the "last value of the loop" (as indicated in the *duplicate question*, which has a clear SSCCE) to be used in *each* callback. That is, none of the intermediate style states are ever set; only the last value (0%) is used. – user2864740 Aug 05 '15 at 04:36

3 Answers3

1

As already said in the comments, you need to put it in the closure. Here's an example:

function timer() {
  for (i = 100; i >= 0; i--) {
    setTimeout(function(t) {
      return function() {
        var timer = t.toString() + "%";
        $('#innerBar').css("width", timer);
      };
    }(i), ((100 - i) * 1000));
  }
}

timer();
#innerBar {height: 50px; background: green; transition: width 0.2s linear}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="innerBar"></div>

EXPLANATION

So my question are: what is going throught function(t)? and why and how does }(i) work? Is it a multiplication of the fu?

Let's take the function body we're passing in to setTimeout:

function(t) {
  return function() {
    var timer = t.toString() + "%";
    $('#innerBar').css("width", timer);
  };
}(i)

Let's omit the inside part:

function(t) {
   // do some stuff with t
}(i)

Looks familiar? It's like the function body is called right away and is called an IIFE, just like, say:

(function(a, b) {
    return a + b;
})(2, 3)  // returns 5

So back to the original function, it accepts one parameter, t, and when we're calling the function we're passing in the iterator i as an argument (so the value of i becomes t inside the function). As I said in the comment, this is necessary in order to "fetch" the current value of i instead of getting the post-loop value.

Shomz
  • 37,421
  • 4
  • 57
  • 85
  • This is a good solution and props for making it a snippet that is executable. I learned that on this post. I had one small thing. Inside the loop you also define a variable `timer` while the function is also called `timer`. While perfectly legal and correct, this might be considered bad practice. For readability I think that variable might be better called `width` or something along those lines. – Mathias Aug 05 '15 at 06:02
  • @Mathias, I completely agree, that was something I didn't like in the OP's example, but decided to keep it in order not to complicate things any further. – Shomz Aug 05 '15 at 13:00
  • I saw this way of using setTimeout elsewhere and I didn't understanded it well enough to actully use it (I mean, I CAN copy paste it and it will work fine, but would perfer understanding it well before doing so). – Bird Aug 05 '15 at 13:50
  • So my question are: what is going throught **function(t)?** and why and how does **}(i)** work? Is it a multiplication? – Bird Aug 05 '15 at 13:53
  • It's a way to make the code read the iterator value AT THE MOMENT the **setTimeout call was made**, because otherwise it will just pass in whatever the iterator value is **when the setTimeout callback is executed** (which is the next iterator value after the looping has finished). – Shomz Aug 05 '15 at 13:54
  • I hope the answer update will help you understand it. – Shomz Aug 05 '15 at 14:04
  • Excellent! It couldn't of been more clear then that. Thanks for your help! – Bird Aug 05 '15 at 14:12
0

I think the following code does what you want. the input time should be 1000, which will decrease you width by 1% every second

  var width = $('#innerBar').width();

  function timeLoop(time){
      width = width*0.99;
      $('#innerBar').css("width", width);
      if (width <= 0.01){
          return;
      }
      else {
          setTimeout(function() {
              timeLoop(time);
          }, time);
      }
  }
Mathias
  • 2,484
  • 1
  • 19
  • 17
David Zhan Liu
  • 474
  • 5
  • 8
  • Hi, David. Please improve your answer a little bit. What is required in `` and I don't think `-1 * time` does what it should. Also inside the `setTimeout` you introduce `selector`. I am not sure where that came from. – Mathias Aug 05 '15 at 06:00
  • Hey there. It makes more sense now. I took the liberty to format your code a bit. You were a little sloppy with the indent and the semicolons. – Mathias Aug 06 '15 at 22:31
0

As @Shomz already posted. That is good solution. I simply want to add my solution because it does not create 100 functions. So it's slightly lighter on the memory. Also you don't have to look through the DOM for #innerBar over and over again. And I removed jQuery as a dependency.

var size = 100;
var bar = document.getElementById( "innerBar" );

function setSize() {
    bar.style.width = size-- + "%";
    if ( size > 0 ) setTimeout( setSize, 1000 );
}
    
setSize();
#innerBar {
    width: 100%;
    height: 50px; 
    background: green; 
    transition: width 0.2s linear;
}
<div id="innerBar"></div>
Mathias
  • 2,484
  • 1
  • 19
  • 17
  • 1
    While chaining is a fine approach, 100 functions - that share the same body - are actually pretty cheap in JavaScript implementations and 'cost' about as much as 100 normal objects - with a few properties assigned - as they basically only need to store the binding context. – user2864740 Aug 05 '15 at 08:23
  • Didn't thought of this one! Thanks, it made me realise I could approche my problem from another direction. – Bird Aug 05 '15 at 14:40