15

Let's say I have three divs, and I'd like each to animate once the previous one is done. Currently, I write this:

$('div1').fadeOut('slow', function() {
    $('div2').fadeOut('slow', function() {
        $('div3').fadeOut('slow');
    });
});

Which is ugly, but manageable.

Now imagine I have 10 different animations that need to happen one after the other on different elements. Suddenly the code gets so clunky that it's extremely hard to manage...

Here's pseudocode for what I'm looking to do:

$('div1').fadeOut('slow' { delay_next_function_until_done: true } );
$('div2').fadeOut('slow' { delay_next_function_until_done: true } );
$('div3').animate({ top: 500 }, 1000 );

How do I achieve this?

gdoron is supporting Monica
  • 136,782
  • 49
  • 273
  • 342
Yuval Karmi
  • 24,847
  • 38
  • 116
  • 172

7 Answers7

22

If you're using a recent version of jQuery, use the animation promises:

$('div1').fadeOut('slow').promise().pipe(function() {
    return $('div2').fadeOut('slow');
}).pipe(function() {
    return $('div3').animate({ top: 500 }, 1000 );
});

You can make it generic:

$.chain = function() {
    var promise = $.Deferred().resolve().promise();
    jQuery.each( arguments, function() {
        promise = promise.pipe( this );
    });
    return promise;
};

var animations = $.chain(function() {
    return $('div1').fadeOut('slow');
}, function() {
    return $('div2').fadeOut('slow');
}, function() {
    return $('div3').animate({ top: 500 }, 1000 );
});

$.when( animations ).done(function() {
    // ALL ANIMATIONS HAVE BEEN DONE IN SEQUENCE
});

Still a lot of function closures but that's the very nature of Javascript. However, it's much more natural and a lot more flexible using Deferreds/Promises since you avoid callbacks "inception".

kangax
  • 37,379
  • 12
  • 94
  • 132
Julian Aubourg
  • 11,068
  • 1
  • 27
  • 28
4

I do that, with this method you can put all divs as you want, only adding or deleting elements in the list var, also you can reorder them and you don't have to worry about the delay time.

var list = [ '#div1', '#div2', '...' ];
var i = 0;
function fade(cb) {
    if (i < list.length) {
        $(list[i]).fadeOut('slow', function() {
            i++;
            fade(cb);
        });
    } else {
        cb && cb();
    }
}
fade();

you can also add a callback when the process ends

fade(function(){alert('end')});

Demo

xgc1986
  • 868
  • 6
  • 8
  • @jfriend00 your code have a bug with the sync option, at the moment an item have sync you will throw another sequence at a time, i put you an example to undestand it `list = [0->sync, 1->nosync, 2->sync, 3->nosync, 4->nosync, 5->nosync, 6->nosync, 7->sync]` it will run `0 1 (because 0 has sync) // 2 (throwed by 0), 3 (throwed by 1), 4 (because o2 has sync) // 5 (throwed by 2), 6 (throwed by 3), 7 (throwed by 4)` and i suposed that the correct run will be `0 1 // 2 3 // 4 // 5 // 6 // 7` – xgc1986 Apr 29 '12 at 11:11
  • The sync option requires that you correctly identify sychronous operations and asychronous operations. Thus `.css()` is `sync: true` and all animations are not. Other than that, I'm not sure what "bug" you're talking about. If this was production code, it could have a list of all known animation methods and detect them autoamtically, but I didn't think that heft was appropriate in my answer. Also, why did you put this as a comment on your answer, not mine (I almost didn't see your comment because of that)? – jfriend00 Apr 29 '12 at 18:22
  • but after an operation with sync, you will throw two operations synchronously, but if you put next operation sync : false, this will be ignores and you will continue throw the next two operations, if you add another sync command, then will be 3 operations at a time and go on – xgc1986 Apr 30 '12 at 13:16
  • You don't seem to understand what the `sync` is used for. It's used only for operations that run immediately like the `.css()` command that do not have a completion function. When used for those types of operations, it does not cause any of the problems you describe and works perfectly fine. See my jsFiddle which has an example of that which does not cause any of the problems you describe. You could have ten `sync` operations in a row and as long as they are actually synchronous operations (things that execute immediately), it would work fine. Do not put sync on an animation. – jfriend00 Apr 30 '12 at 14:43
  • Ups, read another thing xD, i undertood that you used that for css method, only that i missunderstood a think, I'm sorry. But i would don't use the css var, anf if a method is a not a jquery effect i would detect that and made it sync, just only to prevent to the programmer to use the syn var. But sorry for say that you had a bug. – xgc1986 Apr 30 '12 at 20:20
  • Quick note: Deprecation Notice:As of jQuery 1.8, the deferred.pipe() method is deprecated. The deferred.then() method, which replaces it, should be used instead. – Zhanger Sep 01 '13 at 18:50
3

When ever completion functions or callbacks get nested too deep or code is getting repeated over and over again, I tend to think about a data table solution with a common function:

function fadeSequence(list) {
    var index = 0;
    function next() {
        if (index < list.length) {
            $(list[index++]).fadeOut(next);
    }
    next();
}

var fades = ["div1", "div2", "div3", "div4", "div5"];
fadeSequence(fades);

And, if you wanted a different type of animation for some of the items, you could create a array of objects that describe what each successive animation is supposed to be. You could put as much detail in the array of objects as was needed. You can even intermix animations with other synchronous jQuery method calls like this:

function runSequence(list) {
    var index = 0;
    function next() {
        var item, obj, args;
        if (index < list.length) {
            item = list[index++];
            obj = $(item.sel);
            args = item.args.slice(0);
            if (item.sync) {
                obj[item.type].apply(obj, args);
                setTimeout(next, 1);
            } else {
                args.push(next);
                obj[item.type].apply(obj, args);
            }
        }
    }
    next();
}

// sequence of animation commands to run, one after the other
var commands = [
    {sel: "#div2", type: "animate", args: [{ width: 300}, 1000]},
    {sel: "#div2", type: "animate", args: [{ width: 25}, 1000]},
    {sel: "#div2", type: "fadeOut", args: ["slow"]},
    {sel: "#div3", type: "animate", args: [{ height: 300}, 1000]},
    {sel: "#div3", type: "animate", args: [{ height: 25}, 1000]},
    {sel: "#div3", type: "fadeOut", args: ["slow"]},
    {sel: "#div4", type: "fadeOut", args: ["slow"]},
    {sel: "#div1", type: "fadeOut", args: ["slow"]},
    {sel: "#div5", type: "css", args: ["position", "absolute"], sync: true},
    {sel: "#div5", type: "animate", args: [{ top: 500}, 1000]}
];
runSequence(commands);

And, here's a working demo of this second option: http://jsfiddle.net/jfriend00/PEVEh/

jfriend00
  • 580,699
  • 78
  • 809
  • 825
  • 1
    LOL, I thought the concept was to make it simple – Starx Apr 29 '12 at 08:29
  • 2
    The first option is very simple. The second one is reusable for any animation sequence across as many objects as you want without writing any new code each time. You just build an animation table. If you'd rather code the sequence I show in the second one manually, feel free, but the OP asked how to do it without all the nested callbacks so I wrote a generic engine that would handle it. Also keep in mind the OP asked how to make it work with 10 or more sequential and different animations on different objects. I don't see any other solutions that address that part of the question. – jfriend00 Apr 29 '12 at 08:31
2

One way to do this would be to write your own helper function, like so:

$.fn.sequentialFade = function() {
    if(this.length > 0) {
        var $set = $(this);
        $set.eq(0).fadeOut(function() {
            $set.slice(1).sequentialFade();
        });
    }
}

And use it like so:

$('.div1, .div2. .div3').sequentialFade();

http://jsfiddle.net/JpNgv/

Tatu Ulmanen
  • 115,522
  • 31
  • 176
  • 180
  • 1
    Note: this will fade them in DOM sequence order, not necessarily in the order you list them in because jQuery objects order their items in DOM order. – jfriend00 Apr 29 '12 at 07:31
1

try something like:

$( 'div1' ).fadeOut();
$( 'div2' ).delay( 500  ).fadeOut();
$( 'div3' ).delay( 1000 ).fadeOut();

Adjust the timing as necessary

Mark Kahn
  • 81,115
  • 25
  • 161
  • 212
  • 1
    That seems like a really clunky way to do this. Plus, I'd imagine the delay does not guarantee that these events will align, especially when building up a chain of many events. – Brad Apr 29 '12 at 06:53
  • @Brad -- Actually with jQuery it does. jQuery using the timing between the last frame and the current one, so unless the browser locks, they'll only be off by a few ms. – Mark Kahn Apr 29 '12 at 06:55
  • He got 10 elements. instead of writing it 10 times, you can do it with `$(...).each` and use the index for the delay. [check this out](http://stackoverflow.com/a/10370321/601179) – gdoron is supporting Monica Apr 29 '12 at 07:12
1

Use this:

$('#div1, #div2, #div3').each(function(index){
    $(this).delay(1000 * index).hide(1000);
});

If you can give the <div>s a class:

$('.forHide').each(function(index, value){
    $(this).delay(1000 * index).hide(1000);
});​
  • The first element fades out after 1000 * 0 = right away with animation of one second.
  • The second element fades out after 1000 * 1 = One second with animation of one second.
  • The third element fades out after 1000 * 2 = Two seconds with animation of one second.
  • ...
  • ...
  • The n element fades in after 1000 * n = n seconds with animation of one second.

Live DEMO

Community
  • 1
  • 1
gdoron is supporting Monica
  • 136,782
  • 49
  • 273
  • 342
1

Callback is a friend, dont push it away. There are ways to simplify them. Here is one of them

$('div1').fadeOut('slow', div2)
function div3() { $('div3').fadeOut('slow'); }
function div2() { $('div2').fadeOut('slow', div3); }
Starx
  • 72,283
  • 42
  • 174
  • 253