25

I need a callback to execute once after multiple elements have finished animating. My jquery selector is as follows:

$('.buttons').fadeIn('fast',function() {
   // my callback
});

The problem with this is that the buttons class matches a number of elements, all of which need to be faded in before the callback is executed. As it stands now, the callback is executed after each individual element has finished animating. This is NOT the desired function. I'm looking for an elegant solution such that my callback is only executed once after all the matched elements have finished animating. This question has popped up in a few places including SO, but there's never been an elegant answer (nor even a definitive answer for that matter - solutions that work for one person don't work at all for others).

kapa
  • 77,694
  • 21
  • 158
  • 175
Richard
  • 347
  • 1
  • 4
  • 5
  • 2
    maybe do an .each() function and each time the animation is complete for an element, increment a counter. When the counter reaches the number of elements being animated run your callback function? – benhowdle89 Mar 07 '11 at 14:34
  • Possible duplicate of http://stackoverflow.com/questions/2897249/when-animating-how-fire-the-callback-only-when-all-elements-are-done – Chris Shouts Mar 07 '11 at 14:42
  • @Chris, thanks I hadn't seen that question yet - it's perfect! Also, very similar to @Riley and @Ross' answers below. – Richard Mar 07 '11 at 15:01

6 Answers6

83

jQuery introduced a promise in version 1.6 and is far more elegant than adding counters.

Example:

// Step 1: Make your animation
$(".buttons").each(function() {
    $(this).fadeIn("fast");
});

// Step 2: Attach a promise to be called once animation is complete
$(".buttons").promise().done(function() {
    // my callback
});
Sebastian Patten
  • 7,157
  • 4
  • 45
  • 51
  • Does the callback function in done() get called for the completion of each element in $('buttons') or when they are *all* resolved? – TimeEmit Apr 03 '13 at 21:07
  • 1
    @TimeEmit - only once all the elements are done being animated. This is in contrast to the "complete" callback, or even the "done" promise callback you can pass in as an option. – Daniel Schaffer Sep 18 '13 at 20:52
  • I agree that this is a superior solution. If I could designate this as the accepted answer I would. – TimeEmit Sep 19 '13 at 22:44
  • 2
    No need to call the JQuery selector twice or the `each` function (IN THIS SCENARIO... chained animations called upon jquery multiple elements selector will need it) `$(".buttons").fadeIn("fast").promise().done(function { // completion callback });` will suffice – Shockwaver Jun 12 '17 at 07:46
15

For collecting unrelated element animations into a single callback you can do this:

$.when(
    $someElement.animate(...).promise(),
    $someOtherElement.animate(...).promise()
).done(function() {
    console.log("Both animations complete");
});
parliament
  • 21,544
  • 38
  • 148
  • 238
  • 4
    Also note that you can collect these promises in an array and then use `apply` with `when`, like I did: `$.when.apply($, animationPromises).done(function() { console.log("done"; } });` – Mahn May 13 '13 at 18:47
  • 1
    there's no need to call `.promise` on the arguments to `$.when` - it does it for you automatically. – Alnitak Jun 29 '13 at 16:35
  • @Alnitak Beautiful suggestion, didn't know this! +1 – yckart Feb 19 '14 at 02:17
  • @parliament Thanks, just what I was looking for. Here's a jsfiddle example: http://jsfiddle.net/kw546ybf/ – user1379351 Dec 06 '14 at 12:00
  • This answer helped me. I had my promises in array and i used jQuery "apply()" function to call the "when" method. – Tornike Shavishvili Oct 18 '19 at 13:37
5

An alternative to @Ross's answer that will always trigger the callback on the last button to fade in (which may or may not be the last button that was told to animate) could be:

var buttons = $(".buttons");
var numbuttons = buttons.length;
var i = 0;

buttons.fadeIn('fast', function() {
    i++;
    if(i == numbuttons) {
        //do your callback stuff
    }
});
Riley Dutton
  • 7,615
  • 2
  • 24
  • 26
  • 2
    This may seem easy and it does work just fine, but a better way to do this described in the answer by @Underworld below. – aalaap Mar 13 '13 at 06:44
1
var $buttons = $('.buttons');

$buttons.each( function (index) { 
    if ( index == $buttons.length - 1 ) {
        $(this).fadeIn('fast',function() {
           // my callback
        });
    } else {
        $(this).fadeIn('fast');
    }
});

Untested but this should apply the callback to the last button only.

Ross
  • 14,266
  • 12
  • 60
  • 91
0

The idea behind my solution is to keep a counter. Whenever an animation finishes, you simply increment this counter, thus you can see when you are at last button. Remember to set the counter back to zero when everything is done, because you might want to repeat this (hide them again, and show them again).

var $buttons=$('.buttons'),
    button_n=$buttons.length,
    button_counter=0;
$buttons.fadeIn('fast', function () {
    if (button_counter==button_n-1) {
        alert('It is all done!');
        button_counter=0;
    } else {
        button_counter++;
    }
});
kapa
  • 77,694
  • 21
  • 158
  • 175
0
onClickBtnEdit = function(){
    var $EditDel = $($btnEdit).add($btnDel);
    var btns = $EditDel.size();

    $EditDel.fadeOut("def",function(){                   
        btns--;
        if(btns===0){
            $($btnSave).add($btnCancel).fadeIn("slow");
        }
    });
};
DarthJDG
  • 16,511
  • 11
  • 49
  • 56
Fco Diaz
  • 9
  • 1