开发者

Force action to queue from $.each loop

I am animating html elements (cards) using jquery. I need to animate a dealing action ('one you for you, one for me, one you you, one for me...') so want the cards to fully animate (move position) before the next 开发者_JAVA百科one starts. I am creating the cards in a $.each function and calling the animation from there (to avoid looping again unnecessarily). When the card is added to the page the animation starts but the loop then moves on to the next card without waiting for the animation to finish. This makes all the cards appear to animate at exactly the same time rather than sequentially. How do I make the loop wait for the animation callback? Could this be done with triggers? Could I manually queue the items?

A cut down version of my (erroneous) code:

var card = [
  { id:1, pos:{ x:100, y:100 } },
  { id:2, pos:{ x:150, y:105 } },
  { id:3, pos:{ x:200, y:110 } }
];

$.each(card, function() {
  $('#card_' + this.id).animate({ top:this.pos.y, left:this.pos.x });
});

Demo @ http://jsbin.com/akori4


You can't do this in the JavaScript loop, you have to use the callback parameter on the animate call to trigger the animation of the next card. Something like this:

var card = [
    { id:1, pos:{ x:100, y:100 } },
    { id:2, pos:{ x:150, y:105 } },
    { id:3, pos:{ x:200, y:110 } }
];

doOne(0);

function doOne(index) {
    var thisCard = card[index];
    if (thisCard) {
        $('#card_' + thisCard.id).animate({
            top:    thisCard.pos.y,
            left:   thisCard.pos.x
        }, function() {
            doOne(index + 1);
        });
    }
}

Here's a live example Naturally I'd have all this wrapped in a function to avoid creating unnecessary global symbols.


You can use .delay(), like this:

$.each(card, function(i) {
  $('#card_'+this.id).delay(i*400).animate({ top:this.pos.y, left:this.pos.x });
});

Here's your updated/working demo.

Each animation (by default) takes 400ms, and the first parameter to the $.each() callback is the index, so the first is delayed 0*400, the second 1*400, etc...effectively running them one after the other.

Also if you don't need those x/y format variables, you can store them as top/left and pass those animation properties directly, like this:

var card = [
  { id:1, pos:{ left:100, top:100 } },
  { id:2, pos:{ left:150, top:105 } },
  { id:3, pos:{ left:200, top:110 } }
];

$.each(card, function(i) {
  $('#card_'+this.id).delay(i*400).animate(this.pos);
});


Here's another approach:

var card = [
    {elem: $('#card_1'), id:1, pos:{ x:100, y:100 } },
    {elem: $('#card_2'), id:2, pos:{ x:150, y:105 } },
    {elem: $('#card_3'), id:3, pos:{ x:200, y:110 } }
];

(function loop(arr, len){
    if(len--){      
        arr[len].elem.animate({top: arr[len].pos.y, left: arr[len].pos.x}, 400, function(){
           loop(arr, len);
        });     
    }
}(card.reverse(), card.length));

In action: http://jsbin.com/akori4/6/edit


This could get messy with normal callbacks, as each animation then needs to call the next in its callback. Of course you could use a timer or delay to create a pause, but this doesn't guarantee the full animation completes before the next one starts. And there could be a pause between each animation if the animation takes less time than your timeout.

An alternative is to look into Promises and Futures. Although I'm not performing animations, my applications have logic that must be processed in a certain order. I've been using the FuturesJS library to help simplify things. It may look intimidating at first, but it is really very powerful. Take a look at the .chainify() method.

I realize this is probably overkill for your simple use case, but knowing about Promises is something I believe is worthwhile.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜