0

I'm trying to write a script that changes the z-index of 3 images. Basically the script should target the current image and apply a higher z-index on the next image, like a sort of carousel but with a z-index rather then active class. The challenge is to set the z-index after a specific interval. The problem is that the first image is displayed and then the last one. This is my code:

Html:

<div class="changingimages">
    <img src="#" data-time="3000" width="100%" class="alternateimage alternateimage1">
    <img src="#" data-time="2000" width="100%" class="alternateimage alternateimage2">
    <img src="#" data-time="4000" width="100%" class="alternateimage alternateimage3">
</div>

jQuery Script

<script type="text/javascript">

jQuery(document).ready(function(){

    var changeImg = function(i, time, currentImg) {

        setTimeout(function(){

            jQuery(currentImg).next().css("z-index", i);

        }, time);
    };

    var numberOfChilds = jQuery(".changingimages").children().length;
    var currentIndexClass;
    var currentImg;
    var time;

    for (var i=1; i<=numberOfChilds; i++) {

            currentIndexClass = '.alternateimage' + i;
            currentImg = jQuery(currentIndexClass);
            time = jQuery(currentIndexClass).attr("data-time");

            changeImg(i, time, currentImg);

    }

});

I think there is some problem with the closure inside a loop, but not sure!

M. Simon
  • 13
  • 6
  • 3
    *What* is the problem you experience? What does not work? – Bergi Oct 13 '16 at 16:40
  • By the time the callback to `setTimeout` fires, `i` will be the max. – Davin Tryon Oct 13 '16 at 16:41
  • 2
    @DavinTryon: Nope, look a little closer at the code. –  Oct 13 '16 at 16:43
  • It displayed the first image and then the last one. – M. Simon Oct 13 '16 at 16:44
  • What seems to be the problem? I tried to run your code, and it looks like your `setTimeout` function does run (you can test this with a simple `console.log`). What do you expect to happen and what happened instead? maybe something with the logic is wrong? – Thatkookooguy Oct 13 '16 at 16:47
  • @M.Simon Based on your `data-time` values, you should see the second image, the first image, then the third image. Is that not what you were expecting? – Mike Cluck Oct 13 '16 at 16:50
  • 1
    @M.Simon That's expected, yes: for two seconds, there is no z-index; then a z-index of 2 gets applied to the last image (then after 3 seconds, z-index 1 gets applied to the second image, and after 4 seconds a z-index of 3 gets applied to no image). What was the code supposed to actually do? Maybe you want to remove the `.next()`? – Bergi Oct 13 '16 at 16:52
  • The first one to show will be the last image because after the shortest duration (on the second image), you're showing the `.next()`. Because that one will also have the highest zindex, nothing will cover it. –  Oct 13 '16 at 16:53
  • It runs, but the problem is that appear only the first and the last image – M. Simon Oct 13 '16 at 16:53
  • z-index.... it is there just hidden.... You need to set z-index so it is the greatest, not the value in the loop? – epascarello Oct 13 '16 at 16:54
  • 1
    Did you actually want your `data-time` attributes to be out of sequence? We don't know if you intended a duration from the initial function call or from the previous setTimeout –  Oct 13 '16 at 16:55
  • Ok the logic should be this: first image - z-index: 0, after 3 sec should be appear the second image with z-index 1 and after 2 sec the third image with z-index 3. It doesn't matter the data-time on the last image because after there are no more images. – M. Simon Oct 13 '16 at 17:00
  • @squint I use the data-time to set the interval – M. Simon Oct 13 '16 at 17:02
  • 1
    `setTimeout` doesn't pause the script. As it is, the `2000ms` timer will execute first, then the `3000ms` timer, finally the `4000ms` timer. So the last one happens a total of 4 seconds after your loop, not 9 seconds. Doesn't matter the order of the calls. While the answer below gives one solution, I would be inclined to only call the first, and then make a recursive call from within the `setTimeout`. –  Oct 13 '16 at 17:02
  • ... In other words, ditch the loop. –  Oct 13 '16 at 17:04
  • Here's an example of what I mean: https://jsfiddle.net/qc6aeb13/1/ No need to fire off a bunch of `setTimeout` timers immediately in a loop. Let the next timer happen as the result of a call within the timer callback itself. –  Oct 13 '16 at 17:14

2 Answers2

3

It's a common misconception that setTimeout schedules events to run relative to previously queued events. It looks like you believe that, theoretically, the following:

setTimeout(f, 100);
setTimeout(g, 100);
setTimeout(h, 100);

would result in a timeline like this:

0ms   Start
100ms Run f()
200ms Run g()
300ms Run h()

The reality is that the time option in setTimeout means "run this function after at least this much time has passed." Going off of the previous example, you would actually get something more like

0ms   Start
100ms Run f()
101ms Run g()
102ms Run h()

To space out your code correctly, keep adding to the timeout time rather than replacing it.

var time = 0;

for (var i = 1; i <= numberOfChilds; i++) {
  currentIndexClass = '.alternateimage' + i;
  currentImg = jQuery(currentIndexClass);

  // Add to the previous time
  time += parseInt(jQuery(currentIndexClass).attr("data-time"), 10);
  changeImg(i, time, currentImg);
}
Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
0

Here is a fiddle implementing the use of timeout to achieve what you want.

fiddle

.textArea {
  position: absolute;
  height: 50px;
  width: 50px;
  display: block;
}

.box_a {
  background-color: blue;
}

.box_b {
  background-color: red;
}

.box_c {
  background-color: orange;
}

.active {
  z-index: 3;
}


<div class="textArea box_a active">a</div>
<div class="textArea box_b">b</div>
<div class="textArea box_c">c</div>

$(function(){
  var $elem = $('.textArea');

  timeout(0);

  function timeout(i){
    $($elem[i]).addClass('active');
    return setTimeout(function(){
      $elem.removeClass('active');
      i++;
      if(i >= $elem.length){
        i = 0
      }
      timeout(i);
    }, 1000)
  }
});

Note it does not use a for loop, because timeout is asynchronous and will not execute sequentially. Each timeout will fire at the same time basically, then do their action based on the wait time.

The solution is to make a function that keeps track of the index, and when the last timeout has completed execution.

Gabs00
  • 1,869
  • 1
  • 13
  • 12