continuous loop with divs and jquery

sa125 picture sa125 · Nov 18, 2009 · Viewed 18k times · Source

I'm continuing a previous post, but I thought I'd open a new thread since it takes a different approach and has more actual code. Anyway, I'm trying to get an infinite loop going with divs scrolling through a window (the other post has an image, and the code below works).

<html>
    <head>
        <meta http-equiv="Content-type" content="text/html; charset=utf-8">
        <title>Bleh...</title>
        <style type="text/css" media="screen">
            body {
                padding: 0;
                text-align: center;
            }

            #container {
              width: 1000px;
              padding: 10px;
              border: 1px solid #bfbfbf;
              margin: 0 auto;
              position: relative;
            }

            #window {
              width: 400px;
              height: 100px;
              padding: 10px;
              border: 3px solid #666;
              margin: 0 auto;
            }

            .box {
              height: 100px;
              width: 100px;
              border: 1px solid #666666;
              position: absolute;
              z-index: -1;
            }

            .red { background-color: #ff6868;}
            .green { background-color: 7cd980;}
            .blue { background-color: #5793ea;}
            .yellow { background-color: #f9f69e;}
            .purple { background-color: #ffbffc;}
            .cyan { background-color: #bff3ff;}

        </style>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
        <script type="text/javascript" charset="utf-8">
            $(window).load(function() { 
               arrangeBoxes();
               setInterval('shiftLeft()', 3000);
            });

            // arrange the boxes to be aligned in a row
            function arrangeBoxes() {
              $('.box').each( function(i, item) {
                var position = $('#window').position().left + 3 + i * ( $(item).width() + 10 );
                $(item).css('left', position+'px')
              });
            }

            // shifts all the boxes to the left, then checks if any left the window
            function shiftLeft() {
              $('.box').animate({'left' : "-=100px"}, 3000, 'linear');
              checkEdge();
            }

            // returns the new location for the box that exited the window
            function getNewPosition() {
              return $('.box:last').position().left + $('.box:last').outerWidth() + 10;
            }

            // if the box is outside the window, move it to the end
            function checkEdge() {
              var windowsLeftEdge = $('#window').position().left;

              $('.box').each( function(i, box) {

                // right edge of the sliding box
                var boxRightEdge = $(box).position().left + $(box).width();

                // position of last box + width + 10px
                var newPosition = getNewPosition();
                if ( $(box).attr('class').indexOf('red') >=0 ) {
                  console.log('box edge: ' + boxRightEdge + ' window edge: ' + windowsLeftEdge + ' new pos: ' +newPosition);
                }

                if ( parseFloat(boxRightEdge) < parseFloat(windowsLeftEdge) ) { 
                  $(box).css('left', newPosition);
                  $(box).remove().appendTo('#window');
                  first = $('.box:first').attr('class');
                  console.log('first is ' +  first);
                }
              });


            }
        </script>
    </head>
    <body id="index">
        <div id="container">
          <div id="window">
            <div class="box red"></div>
            <div class="box green"></div>
            <div class="box blue"></div>
            <div class="box yellow"></div>
            <div class="box purple"></div>
            <div class="box cyan"></div>
          </div>
        </div>
    </body>
</html>

Anyway, the problem is that I can get the boxes to move left, but I can't get the leading box to pop back to the end of the line when I run the whole thing. When I run each function separately (using firebug) -

>> $('.box').animate({'left' : "-=100px"}, 3000, 'linear');
>> checkEdge()

It works great. When I put them together, I just get continuous motion right-to-left until the boxes leave the screen, so it must be how they interact. I hope this makes sense (if not, save the above code block as an html file and run it in a browser). I've been stuck on this for a while, so any help will be awesome. Thanks.

Answer

jitter picture jitter · Nov 18, 2009

You need to set the callback like this

$('.box').animate({'left' : "-=100px"}, 3000, 'linear', checkEdge());

checkEdge() not checkEdge

Check here for a working solution http://jsbin.com/uceqi (just copy pasted your code)


Here it actually makes a difference if you leave the braces away. The version with braces executes checkEdge() once per animation (to be honest in reality it executes checkEdge BEFORE the animation starts. The checkEdge()function is called when the $...animte(..) statement is reached even before animate() itself executes. checkEdge would cause the function to be passed in as parameter, normally the correct way to go).

The version without braces executes checkEdge once for the number of elements matched with the selector. In this case 6 times.

So technically speaking your approach would be correct as it would fire the callback once per animated element and AFTER the animation. But executing checkEdge once this obviously isn't what the author intended (check loop over divs in checkEdge). And also causes serious lagging as you loop several times over all divs

6divs animated -> 6x callback fired -> callback loops over all divs ==> 36x execution of anonymous function in checkEdge()!!

So the actual problem is that the callback gets fired 6x and all at the same time. Thus we have a race condition and the animation messes up. As several checkEdge() calls work concurrently on repositioning the divs.

But you only notice this because the animation-speed and the interval in setInterval (shiftLeft) are identical. Would they be different ((interval +~1000ms) > animation-speed) then it would work correctly too. But of course the animation wouldn't be fluid. As shitfLeft() would fire 1s after the animation stopped.

You can check this sample http://jsbin.com/iwapi3 which illustrates what happens when use the version without braces. Wait for sometime and the counter for checkEdge() calls increases by 7 every 3s. You will also notice that in some cases the animation partially works and some divs are moved back to end of the list (sometimes).

After sometime click the button and watch how the version with braces sorts the mess out after a few runs (but by now the correct order of the color is of course messed up). And only calls checkEdge() once per animation. (Shortly after hitting the button you will get another +7 as some callbacks are still in the pipe)