views:

249

answers:

3

Here's the basic idea of what I'm trying to do:

  1. Set the innerHTML of a DIV to some value X
  2. Animate the DIV
  3. When the animation finishes, change the value of X and repeat N times

If I do this in a loop, what ends up happening is, because the animations occur asynchronously, the loop finishes and the DIV is set to its final value before the animations have had a chance to run for each value of X.

As this question notes, the best way to solve this problem is to make a recursive call to the function in the callback handler for the animation. This way the value of the DIV doesn't change until the animation of the previous value is complete.

This works perfectly...to a point. If I animate a bunch of these DIVs at the same time, my browser gets overwhelmed and crashes. Too much recursion.

Can anyone think of a way to do this without using recursion?

EDIT:

Here's my code:

+2  A: 

Using setInterval, you should be able to do something like the following. There's no recursion taking place at all. (Of course, this example is contrived, but should explain the concept.)

Working example: http://jsfiddle.net/TNwAZ/1/

HTML

<div id='myDiv' style="position:relative">div</div>​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

Javascript

var count = 10;
var duration = 500;

var interval = setInterval(function() { 
                                 $('#myDiv').text(count).animate({left: '+=50'},duration);
                                 count--;      // Decrement count
                                 if(!count) { clearTimeout(interval) }
                           }, duration );​

EDIT:

Not sure how you are getting the elements to animate, but here's an example of placing references to them in an array, and looping over that.

http://jsfiddle.net/TNwAZ/3/

HTML

<div id='myDiv1' style="position:relative">div 1</div>
<div id='myDiv2' style="position:relative">div 2</div>
<div id='myDiv3' style="position:relative">div 3</div>

Javascript

var divArray = ['myDiv1','myDiv2','myDiv3'];

for(var i in divArray) {
    intervalAnimate(divArray[i]);
}

function intervalAnimate(theDiv) {
    var count = 10;
    var duration = 500;

    var interval = setInterval(function() { 
              $('#' + theDiv).text(count).animate({left: '+=50'},duration);
               count--; // Decrement count
               if(!count) { clearTimeout(interval) }
       }, duration );
}

EDIT:

This version skips the for loop, and just gets a collection of jQuery objects, and passes that in.

http://jsfiddle.net/TNwAZ/5/

HTML

<div id='myDiv1' style="position:relative">div 1</div>
<div id='myDiv2' style="position:relative">div 2</div>
<div id='myDiv3' style="position:relative">div 3</div>​

Javascript

var $divs = $('div[id^=myDiv]');

intervalAnimate($divs);

function intervalAnimate(collection) {
    var count = 10;
    var duration = 500;

    var interval = setInterval(function() { 
              $(collection).text(count).animate({left: '+=50'},duration);
               count--; // Decrement count
               if(!count) { clearTimeout(interval) }
       }, duration );
}
patrick dw
I like this idea, but I can't seem to get this to work on multiple divs. That is, if you wrap this in a function and call it in a for loop for myDiv1, myDiv2, myDiv3, etc. to animate them all at once.
Rob Sobers
I'll update my answer with a couple of solutions.
patrick dw
Thanks! I modified my code to use setInterval (and it works!), but I'm having a timing problem. I have to crank up the duration of the interval way higher than the sum of each animation otherwise it starts stepping on itself. Would you mind taking a look at the code I edited in to my question?
Rob Sobers
I assume you're updating right now, because I don't see any code. Anyway, are you saying that you want ( **number of elements animated** x **duration of animation** = **duration of interval** )?
patrick dw
The code you posted appears to be the recursive version.
patrick dw
In your example, you do 1 animation on a single element for 500 milliseconds. In my example, I do 3 animations on a single element for X seconds. Even if I set my interval to X seconds, the timing is off and I'm not sure why.
Rob Sobers
I posted the setInterval version in my latest edit. You'll see that you have to crank the duration up to about 150 milliseconds for it to (sort of) have the right effect. It could be jQuery/browser oddness. I'm thinking I'm going to resort to using CSS sprites. Thanks so much for the awesome help!
Rob Sobers
@Rob - Is there a reason for all the 1 millisecond animations with a callback? I replaced them with calls to `css()` instead. Got rid of the `.children().stop(true)` as well. Didn't seem necessary. Please take a look at the updated version and confirm that it still behaves the way you expect. http://jsfiddle.net/W7aFm/5/ Unfortunately, I'm leaving to go out of town and won't be back until Thursday morning. I'll have another look at that time if you haven't figured it out.
patrick dw
@Rob - By the way, I remember hearing something about IE having a minimum duration for setTimeout and/or setInterval. Not sure about that, but if you're using IE, maybe there's an issue there.
patrick dw
@Rob Sobers: Well, this works for me. Give it a try and let me know what you think. http://jsfiddle.net/W7aFm/6/
patrick dw
@Rob Sobers: I went back to using local variables instead of object properties, as it should perform a little better. http://jsfiddle.net/W7aFm/7/
patrick dw
@patrick - that solution is perfect! Thanks so much for all your efforts. Coders like you make this site so awesome.
Rob Sobers
@patrick - actually, one more tiny thing :). I actually meant to start `.letter-front` off with a height of 0 and then each interval should animate it like so: height+=12, height+=12 then back to 0. Revealing half a letter at a time gives a flap effect. When I make this change, it appears that the next interval steps on the previous and the div ends up growing and growing. Any ideas?
Rob Sobers
@Rob Sobers: http://jsfiddle.net/W7aFm/9/ First, this updated doesn't fix the problem. I moved the `clearInterval(...)` line to the bottom of the `setInterval` function in order to avoid a `-1` fix that I was using, which would cause trouble if the downward direction goes the other way. I'll post another comment below as to the issue at hand.
patrick dw
@Rob Sobers: So the issue seems to be that the duration of the animation and the setInterval are slightly out of sync even though they use the same number. If you pass in a static value of say 500 instead of the random number below 50, you can sort of see what's happening. So say the value of rate is "500", but you add an additional "20" to the rate of the setInterval `(rate + 20)`, the effect seems to work (at that speed anyway). When go back to the random faster rate, it is moving too fast for me to tell if it is working. I would say to tinker with the rate value for the setInterval a bit.
patrick dw
@Rob Sobers: One other way to see the issue is to change the 'rate' to a static value of 500, and add `.stop()` to the `boxFront`, as in `boxFront.stop().html(String.fromCharCode(from)).css(...`. This is not a solution, because it causes the callback to not run (therefore boxBack doesn't get updated), but it does illustrate that the height effect is running properly. I'll try to tinker a bit more. In the meantime, play around with adding a little time to the rate of the setInterval duration.
patrick dw
@patrick I see what you're talking about. You'd think if the interval is set to 500 and the sum of the animations are 500 that it'd be in sync. Oh well, that's okay. No need to tinker anymore; it's been a great learning experience and I've got a workable solution. Thanks again!
Rob Sobers
A: 

I haven't tested this code, but have you tried something like:

var x = ["one", "two", "three"];
function animate(e, init){
    e.data("index", (init ? 0 : e.data("index") + 1));
    e.html(x[e.data("index")]);
    e.animate({ key: val }, { duration: 500, complete: function(){
        if ($(this).data("index") < x.length - 1) {
            animate($(this));
        }
    }});
}
$(".toAnimate").each(function(){
    animate($(this), true);
});

Granted, there is recursion, but maybe none of the kind you're hoping to eliminate? It might end up being something that's limited by the browser, unfortunately. (Although, I can't think of why having a longer .queue("fx")would itself affect performance.)

Also there could be different x values for each .toAnimate if you set it up (x) using an indexed object.

JKS
+1  A: 

Setup an end of animation function. If you want to execute 'N' animations create an array and add 'N' elements to it. Each time you execute the end of animation function slice() off one of the array elements. When it's empty then you're done, otherwise start the next animation.

Jay