【问题标题】:Force action to queue from $.each loop强制操作从 $.each 循环排队
【发布时间】:2010-09-16 18:52:26
【问题描述】:

我正在使用 jquery 为 html 元素(卡片)制作动画。我需要动画处理动作(“一个你给你,一个给我,一个你你,一个给我......”)所以希望卡片在下一个开始之前完全动画(移动位置)。我在 $.each 函数中创建卡片并从那里调用动画(以避免不必要地再次循环)。当卡片被添加到页面时,动画开始,但循环然后移动到下一张卡片而不等待动画完成。这使得所有卡片看起来都在完全相同的时间而不是按顺序进行动画处理。如何让循环等待动画回调?这可以用触发器来完成吗?我可以手动排队吗?

我的(错误)代码的精简版:

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 });
});

演示@http://jsbin.com/akori4

【问题讨论】:

    标签: javascript jquery triggers


    【解决方案1】:

    你不能在 JavaScript 循环中这样做,你必须使用 animate 调用上的回调参数来触发下一张卡片的动画。像这样的:

    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 当然,我会将所有这些都包装在一个函数中,以避免创建不必要的全局符号。

    【讨论】:

    • +1 正确答案,正确设计的异步函数,如animate,总是能够在完成后调用另一个函数。这是触发下一个动画的地方。
    【解决方案2】:

    你可以使用.delay(),像这样:

    $.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.

    每个动画(默认)耗时400ms,$.each()回调的第一个参数是索引,所以第一个延迟0*400,第二个1*400,以此类推...有效地运行它们之后另一个。

    此外,如果您不需要那些x/y 格式变量,您可以将它们存储为top/left 并直接传递这些动画属性,如下所示:

    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);
    });
    

    【讨论】:

    • 一堆卡,时间的不确定性很可能会成为问题。
    • @TJCrowder - 不会的,它是 setInterval() 在幕后。
    • @Nick: setInterval 有所有种类的不确定性。
    • @TJCrowder - 我同意,但它们在内部是一致的,延迟一个延迟另一个。
    • @Nick:不,每个间隔都独立于其他间隔。他们交互的唯一时间是当一个时间间隔到期但另一个时间间隔的代码已经在运行,延迟它。那就是它变得丑陋的时候。
    【解决方案3】:

    这是另一种方法:

    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));
    

    实际操作中:http://jsbin.com/akori4/6/edit

    【讨论】:

    • 这种方法颠倒了卡片动画顺序:)
    • @Nick:确实如此,但这只是另一种处理方式。要恢复原来的顺序,很容易解决。
    • 对我来说,这与尼克的方法有相同的时间问题(或者没有,根据尼克!)。
    • @T.J.我同意。我意识到我实际上没有理由使用以前的模式,所以我改变了它,将下一个调用放入 .animate() 的回调中。
    • @jAndy:是的,这也是我的首选方法。顺便说一句,仅供参考,在 IE 上,您在那里创建了两个函数。 ;-) 每当您有一个命名函数声明时,您也可以将其用作表达式的右侧,MS 的 JScript 会创建其中两个——一个来自声明,另一个来自表达式。您的第一个调用是调用由表达式生成的函数,然后调用由声明创建的函数。在这种情况下无关紧要,但我想指出这一点,因为对于某些模式,人们最终会得到他们创建的几乎每个函数的两个副本! :-)
    【解决方案4】:

    这对于普通的回调可能会变得混乱,因为每个动画都需要在其回调中调用下一个。当然,您可以使用计时器或延迟来创建暂停,但这并不能保证完整动画在下一个动画开始之前完成。如果动画花费的时间少于您的超时时间,则每个动画之间可能会有暂停。

    另一种方法是研究 Promises 和 Futures。虽然我没有执行动画,但我的应用程序具有必须按特定顺序处理的逻辑。我一直在使用FuturesJS 库来帮助简化事情。乍一看可能很吓人,但它确实非常强大。看看.chainify() 方法。

    我意识到这对于您的简单用例来说可能有点矫枉过正,但我​​认为了解 Promises 是值得的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-24
      • 2015-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-12
      • 2015-07-24
      • 1970-01-01
      相关资源
      最近更新 更多