如何使用jQuery逐个动画多个元素?

34

我以为这很简单,但我仍然无法让它起作用。通过点击一个按钮,我希望发生几个动画 - 依次进行 - 但是现在所有的动画都同时发生了。以下是我的代码 - 请问哪里出错了吗?:

$(".button").click(function(){
  $("#header").animate({top: "-50"}, "slow")
  $("#something").animate({height: "hide"}, "slow")
  $("ul#menu").animate({top: "20", left: "0"}, "slow")
  $(".trigger").animate({height: "show", top: "110", left: "0"}, "slow");
});

6.9kb是有注释、未压缩和未经过Gzip处理的。实际上可能小于1kb。 - redsquare
1
我也想实现类似的功能,因此我创建了一个小型Jquery插件(https://github.com/fillswitch/Jquery-Sequential-Animations)。希望能帮助到一些人! - Fillip Peyton
10个回答

30

只有当你在为同一个元素进行动画处理时,队列才能起作用。不知道为什么上面的回答会被投票,但它不能正常工作。

你需要使用动画回调函数。你可以将一个函数作为animate函数的最后一个参数传入,它将在动画完成后被调用。然而,如果你有多个嵌套的带回调函数的动画,脚本将变得非常难读。

我建议使用以下插件,它重写了原生的jQuery animate函数,并允许您指定队列名称。所有添加到相同队列名称的动画将按顺序运行,如此处所示。

示例脚本

  $("#1").animate({marginTop: "100px"}, {duration: 100, queue: "global"});
  $("#2").animate({marginTop: "100px"}, {duration: 100, queue: "global"});
  $("#3").animate({marginTop: "100px"}, {duration: 100, queue: "global"});

是的,回调函数正是我想建议的。 - Matt Sach
感谢redsquare。但是由于不会有太多的动画效果,我认为没有必要使用插件(额外6.26kb)。不过我会记住这个建议,以备将来之需。 - lnvrt
2
两个链接似乎都失效了,请 @redsquare 检查一下。 - Shawn
该项目不再更新。这也不是答案。 - Guillermo Siliceo Trueba

27

我知道这是一个老问题,但是应该为更新的jQuery版本(1.5及以上)提供答案:

使用$.when函数,你可以编写这个帮助程序:

function queue(start) {
    var rest = [].splice.call(arguments, 1),
        promise = $.Deferred();

    if (start) {
        $.when(start()).then(function () {
            queue.apply(window, rest);
        });
    } else {
        promise.resolve();
    }
    return promise;
}

那么你可以这样调用它:

queue(function () {
    return $("#header").animate({top: "-50"}, "slow");
}, function () {
    return $("#something").animate({height: "hide"}, "slow");
}, function () {
    return $("ul#menu").animate({top: "20", left: "0"}, "slow");
}, function () {
    return $(".trigger").animate({height: "show", top: "110", left: "0"}, "slow");        
});

2
这是一个非常好的模式。再提一个建议 - IE 8(至少)以不同的方式处理“arguments”对象。首先,它需要被转换为真正的数组,然后.splice方法需要两个参数。使用 var args = Array.prototype.slice.call(arguments); var rest = [].splice.call(args, 1, args.length-1); - jschrab
1
谢谢,这对我很有帮助。 - Dave Munger

26

你可以使用一系列回调函数。

$(".button").click(function(){
    $("#header").animate({top: "-50"}, "slow", function() {
        $("#something").animate({height: "hide"}, "slow", function() {
            $("ul#menu").animate({top: "20", left: "0"}, "slow", function() {
                $(".trigger").animate({height: "show", top: "110", left: "0"}, "slow");        
            });
        });
    });
});

4
我在我的网站上遇到了类似的问题,后来考虑使用回调函数来解决,但这种方法似乎有些不太优雅。如果能用jQuery链式操作来实现就更好了。目前我是用延迟函数完成的:先动画显示第一件事情,然后用与第一件事情相同的时间延迟第二件事情,再进行动画显示...以此类推。 - MrVimes
但是如何同时播放动画。这种方式会一个接一个地播放动画。 - Wasim A.
1
能否使它更通用?如果您不知道有多少元素怎么办?我尝试了一个for循环,但似乎对此无效。 - www139
完全未经测试...var items = [{element: element, properties: {'top': '-50}, speed:'slow'}, ...] $('button.click(animateQueue(items)); function animateQueue(items) { if (! items.length) { return; } var item = items.shift(); $(item.element).animate(item.properties, item.speed, animateQueue.bind(this, items)); } - jammus
如果每个动画都有缓动,是否可能将所有动画缓动,就像是同一个动画一样? - bazzlebrush

6
@schmunk的答案有所改进,可以使用纯对象的jQuery对象队列来避免与其他不相关的动画产生冲突:
$({})
    .queue(function (next) {
        elm1.fadeOut('fast', next);
    })
    .queue(function (next) {
        elm2.fadeIn('fast', next);
    })
    // ...

需要翻译的内容:

需要记住的一件事是,虽然我从未在这方面遇到过问题,但据文档所述,在普通对象包装器上使用队列方法并不得到官方支持。

使用普通对象

目前,jQuery只支持对被包装在普通JavaScript对象中的以下操作:.data()、.prop()、.bind()、.unbind()、.trigger()和.triggerHandler()。


2
这是一个优雅的解决方案,因为它避免了深层嵌套的回调函数。我已经组装了一个小的jsFiddle来实现这个解决方案。关于支持包装普通对象的问题:这个官方的jQuery教程使用了相同的方法。 - Philipp

2

扩展jammus的答案,对于长序列的动画来说,这可能更实用。发送一个列表,依次对每个进行动画处理,使用减少的列表递归调用animate函数。当所有动画完成时执行回调函数。

这里的列表是选定元素的列表,但它也可以是一个更复杂的对象列表,每个对象持有不同的动画参数。

这是一个演示

$(document).ready(function () {
    animate([$('#one'), $('#two'), $('#three')], finished);
});

function finished() {
    console.log('Finished');
}

function animate(list, callback) {
    if (list.length === 0) {
        callback();
        return;
    }
    $el = list.shift();
    $el.animate({left: '+=200'}, 1000, function () {
        animate(list, callback);
    });
}

2

您还可以将您的效果放入相同的队列中,即BODY元素的队列。

$('.images IMG').ready(
   function(){
        $('BODY').queue(
            function(){
                $('.images').fadeTo('normal',1,function(){$('BODY').dequeue()});
            }
        );
    }
);

请确保您在最后一个effect回调中调用dequeue()。


2

按顺序逐个动画多个标签

如果您只选择像body这样的标签进行全局排队,您可以利用jQuery内置的动画队列:

// Convenience object to ease global animation queueing
$.globalQueue = {
    queue: function(anim) {
        $('body')
        .queue(function(dequeue) {
            anim()
            .queue(function(innerDequeue) {
                dequeue();
                innerDequeue();
            });
        });
        
        return this;
    }
};

// Animation that coordinates multiple tags
$(".button").click(function() {
    $.globalQueue
    .queue(function() {
        return $("#header").animate({top: "-50"}, "slow");
    }).queue(function() {
      return $("#something").animate({height: "hide"}, "slow");
    }).queue(function() {
        return $("ul#menu").animate({top: "20", left: "0"}, "slow");
    }).queue(function() {
        return $(".trigger").animate({height: "show", top: "110", left: "0"}, "slow");
    });
});

http://jsfiddle.net/b9chris/wjpL31o0/

这是为什么它能够工作以及它的实现原理:

  1. 调用 $.globalQueue.queue() 会将你的标签动画函数推入队列,但是会将其推入到 body 标签的队列中。

  2. 当 jQuery 到达 body 队列中的你的标签动画函数时,标签动画就开始了,并且该动画队列(此例中为 body 的队列)会在自定义动画回调函数调用传入的 dequeue() 函数之前暂停。因此,尽管你的标签动画和 body 的动画队列是分开的,但是 body 标签的队列现在正在等待它的 dequeue() 被调用。http://api.jquery.com/queue/#queue-queueName-callback

  3. 我们只需要将标签队列中最后一个被推入的函数设为调用全局队列的 dequeue() 函数,这就是将队列连接起来的方式。

  4. 为了方便,globalQueue.queue 方法返回 this 引用,以便进行链式操作。

setInterval

为了完整起见,如果你只是想按时间间隔触发动画,而不是使独立的动画协调,那么可以像这样替换调用 setInterval

setInterval(doAthing, 8000);

使用这个:

/**
 * Alternative to window.setInterval(), that plays nicely with modern animation and CPU suspends
 */
$.setInterval = function (fn, interval) {
    var body = $('body');
    var queueInterval = function () {
        body
        .delay(interval)
        .queue(function(dequeue) {
            fn();
            queueInterval();
            dequeue();  // Required for the jQuery animation queue to work (tells it to continue animating)
        });
    };
    queueInterval();
};

$.setInterval(doAthing, 8000);

这里是一个示例链接。

同时,避免浏览器重新启用背景标签动画时出现的尴尬的动画爆炸。


1

这个问题已经得到了很好的回答(我认为jammus的回答是最好的),但我想提供另一个选项,基于我在我的网站上如何使用delay()函数来解决这个问题...

  $(".button").click(function(){
     $("#header").animate({top: "-50"}, 1000)
     $("#something").delay(1000).animate({height: "hide"}, 1000)
     $("ul#menu").delay(2000).animate({top: "20", left: "0"}, 1000)
     $(".trigger").delay(3000).animate({height: "show", top: "110", left: "0"}, "slow");
});

(将1000替换为您想要的动画速度。这个想法是您的延迟函数按那个量延迟并累计每个元素的动画延迟,所以如果您的动画每个500毫秒,您的延迟值将是500、1000、1500)

Edit:FYI,jQuery中“slow”速度也是600毫秒。因此,如果您仍想在动画中使用“slow”,只需在每个后续调用延迟函数中使用这些值- 600、1200、1800即可。


这似乎不是一种非常动态的方法,因为您必须手动更新这些值。 - neufuture
这是我过去的做法,也是我来到这里的原因,即寻求更好的方法,但这确实可行,您可以设置变量来控制动画的持续时间,并在延迟中使用它们,这样您只需要像平常一样更新持续时间... 然后似乎没有任何不利影响? - Dean_Wilson
一个简单而快捷的解决方案 +1 - suspectus
1
它要求用户知道或计算延迟持续时间 - 这是不动态的(如上所述)。 - Igor

1

我在考虑一种回溯解决方案。

也许,您可以定义这里的每个对象都具有相同的类,例如.transparent

然后,您可以编写一个名为startShowing的函数,查找第一个具有.transparent类的元素,对其进行动画处理,删除.transparent,然后调用自身。

我不能保证顺序,但通常按照文档编写的顺序进行。

这是我做的一个函数来尝试它

function startShowing(){
      $('.pattern-board.transparent:first').animate(
        { opacity: 1}, 
        1000,
        function(){
          $(this).removeClass('transparent');
          startShowing();
        }
      );
    }

-8
使用 queue 选项:
$(".button").click(function(){
  $("#header").animate({top: "-50"}, { queue: true, duration: "slow" })
  $("#something").animate({height: "hide"}, { queue: true, duration: "slow" })
  $("ul#menu").animate({top: "20", left: "0"}, { queue: true, duration: "slow" })
  $(".trigger").animate({height: "show", top: "110", left: "0"}, { queue: true, duration: "slow" });
});

2
@garrett,在回答之前你测试过那个吗? - redsquare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接