34

I thought it would be simple but I still can't get it to work. By clicking one button, I want several animations to happen - one after the other - but now all the animations are happening at once. Here's my code - can someone please tell me where I'm going wrong?:

$(".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"); }); 
2
  • The 6.9kb is commented, un-minified and un-gzipped. Prob <1kb in reality Commented Aug 2, 2009 at 14:12
  • 1
    I was looking to do a very similar thing as this question, so I created a small Jquery plugin (github.com/fillswitch/Jquery-Sequential-Animations). Hope it helps some others out! Commented Oct 17, 2013 at 17:23

10 Answers 10

30

Queue only works if your animating the same element. Lord knows why the above got voted up but it will not work.

You will need to use the animation callback. You can pass in a function as the last param to the animate function and it will get called after the animation has completed. However if you have multiple nested animations with callbacks the script will get pretty unreadable.

I suggest the following plugin which re-writes the native jQuery animate function and allows you to specify a queue name. All animations that you add with the same queue name will be run sequentially as demonstrated here.

Example script

 $("#1").animate({marginTop: "100px"}, {duration: 100, queue: "global"}); $("#2").animate({marginTop: "100px"}, {duration: 100, queue: "global"}); $("#3").animate({marginTop: "100px"}, {duration: 100, queue: "global"}); 
Sign up to request clarification or add additional context in comments.

4 Comments

Yep, callbacks were what I was going to suggest.
Thanks redsquare. But since there's not gonna be a whole lot of animations going on I don't think there's a need for a plugin (an extra 6.26kb). I'll keep it in mind for future though.
both links seem broken, could you check them please @redsquare?
That project is no longer being updated. This is not the answer either.
27

I know this is an old question, but it should be updated with an answer for newer jQuery versions (1.5 and up):

Using the $.when function you can write this helper:

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

Then you can call it like this:

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

1 Comment

It IS a very nice pattern. One more suggestion - IE 8 (at least) deals with the "arguments" object differently. First, it needs to be made a true Array, then the .splice method needs both parameters. Use var args = Array.prototype.slice.call(arguments); var rest = [].splice.call(args, 1, args.length-1);
26

You could do a bunch of callbacks.

$(".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"); }); }); }); }); 

5 Comments

I had a similar issue on my website, and I've since thought of using the callbacks, but it seems slightly inelegent to do so. it'd be nice if you could somehow do it with jquery chaining. at the moment I do it using the delay function : animate the first thing. delay the second thing by the same amount of time I'm animating the first thing, then animate it.. and so on.
but how to animate simultaneously. this will animate one after another.
Is it possible to make it more generic. What if you don't know how many elements there are? I've tried a for loop but it doesn't seem to work for that.
Completely untested... 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)); }
If you had easing on each animation, is it possible to ease all of the animations as if it's the same animation?
6

A slight improvement on @schmunk's answer is to use a plain object jQuery object's queue in order to avoid conflicting with other unrelated animations:

$({}) .queue(function (next) { elm1.fadeOut('fast', next); }) .queue(function (next) { elm2.fadeIn('fast', next); }) // ... 

One thing to keep in mind is that, although I have never run into problems doing this, according to the docs using the queue methods on a plain object wrapper is not officially supported.

Working With Plain Objects

At present, the only operations supported on plain JavaScript objects wrapped in jQuery are: .data(),.prop(),.bind(), .unbind(), .trigger() and .triggerHandler().

1 Comment

This is an elegant solution as it avoids deeply nesting callbacks. I've assembled a little jsFiddle that puts this into action. Regarding the support of wrapped plain objects: this official jQuery tutorial uses the same approach.
3

Animate Multiple Tags Sequentially

You can leverage jQuery's built-in animation queueing, if you just select a tag like body to do global queueing:

// 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/

So, here's why this works, and what it's doing:

  1. The call to $.globalQueue.queue() is just queueing a call to your tag's animation, but it queues it on the body tag.

  2. When jQuery hits your tag animation in the body queue, your tag's animation starts, on the queue for your tag - but the way the jQuery animation framework works, any custom animation callback causes a tag's animation queue (the body's in this case) to halt, until the custom animation calls the passed-in dequeue() function. So, even though the queues for your animated tag and body are separate, the body tag's queue is now waiting for its dequeue() to be called. http://api.jquery.com/queue/#queue-queueName-callback

  3. We just make the last queued item on the tag's queue a call to continue the global queue by calling its dequeue() function - that's what ties the queues together.

  4. For convenience the globalQueue.queue method returns a this reference for easy chaining.

setInterval

For the sake of completeness, it's easy to land here just seeking an alternative to setInterval - that is you're not so much looking to make separate animations coordinate, as just fire them over time without the strange surge ahead in your animation caused by the way newer browsers will postpone animation queues and timers to save CPU.

You can replace a call to setInterval like this:

setInterval(doAthing, 8000); 

With this:

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

http://jsfiddle.net/b9chris/h156wgg6/

And avoid those awkward blasts of animation when a background tab has its animations re-enabled by the browser.

Comments

2

You can also put your effects into the same queue, i.e. the queue of the BODY element.

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

Make sure you call dequeue() within the last effect callback.

Comments

2

Extending on jammus' answer, this is perhaps a bit more practical for long sequences of animations. Send a list, animate each in turn, recursively calling animate again with a reduced list. Execute a callback when all finished.

The list here is of selected elements, but it could be a list of more complex objects holding different animation parameters per animation.

Here is a fiddle

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

Comments

1

This has already been answered well (I think jammus's answer is the best) but I thought I'd provide another option based on how I do this on my website, using the delay() function...

 $(".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"); }); 

(replace 1000 with your desired animation speed. the idea is your delay function delays by that amount and accumulates the delay in each element's animation, so if your animations were each 500 miliseconds your delay values would be 500, 1000, 1500)

edit: FYI jquery's 'slow' speed is also 600miliseconds. so if you wanted to use 'slow' still in your animations just use these values in each subsequent call to the delay function - 600, 1200, 1800

4 Comments

This doesn't seem like a very dynamic method seing that you have to manually update these values.
This is how I've done it in the past and the reason I ended up here ie to get a better method but this does work, and you could set variables for the duration of the animations and use them in the delay so that you would only have to update the duration as normal... then there's seemingly no downside??
A simple quick solution +1
It requires the user to either know or calculate the delay duration - which is not dynamic (as pointed out above).
1

I was thinking about a backtracking solution.

Maybe, you can define that every object here has the same class, for example .transparent

Then you can make a function, say startShowing, that looks for the first element which has the .transparent class, animate it, remove .transparent and then call itself.

I can't assure the sequence but usually follows the order in which the document was written.

This is a function I did to try it out

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

Comments

-8

Use the queue option:

$(".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" }); }); 

1 Comment

@garrett, did you test that before giving the answer?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.