27

Let's say that I have several animations running at once, and I want to call a function once all of them are finished.

With only one animation, it's easy; there's a callback for that. For example :

$(".myclass").fadeOut(slow,mycallback);

Trouble is, if my selector finds several items, the callback will be called for each of them.

A workaround is not too hard; for example :

<!DOCTYPE html>
<html>
<head>
  <title>Example</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
  <script type="text/javascript">
    $(document).ready(function() {
      var $mc=$(".myclass"),l=$mc.length;
      $mc.fadeOut("slow",function(){
        if (! --l) $("#target").append("<p>All done.</p>");
      });
    });
  </script>
  <style type="text/css">
  </style>
</head>
<body>
  <p class="myclass">Paragraph</p>
  <p class="myclass">Paragraph</p>
  <p class="myclass">Paragraph</p>
  <p class="myclass">Paragraph</p>
  <p class="myclass">Paragraph</p>
  <p class="myclass">Paragraph</p>
  <div id="target"></div>
</body>
</html>

My question is : is there a better way to do this ?

David V.
  • 5,708
  • 3
  • 27
  • 27
  • In most cases, I use a boolean instead of a counter (so the action is performed at the end of the first animation), but otherwise it's the same method. Good question, I'm curious for other ways. – Tom Bartel Mar 12 '10 at 11:25
  • Indeed. You want to wait for all of them if for example all your animations are not of the same length (not like in my simplistic example), and you want to do something only after things stop moving, or fading on your browser. – David V. Mar 12 '10 at 11:32

3 Answers3

48

Look at the discussion on this thread: jQuery $.animate() multiple elements but only fire callback once

The .when()/.then() and the .promise().done(callback()); functions provide a much more elegant solution to the problem of calling a single calllback at the end of all animations.

Community
  • 1
  • 1
Scrat
  • 544
  • 6
  • 9
  • 2
    Good call. Similarly, this uses queue to fire a callback after the last element is done aniamting: elements.fadeOut(330).last().queue( function(){ } ); – Tyler May 16 '12 at 20:01
29

You can run the same callback for all of them, but only execute the if clause if none are currently being animated anymore, like this:

  $(".myclass").fadeOut("slow", function() {
    if ($(".myclass:animated").length === 0)
      $("#target").append("<p>All done.</p>");
  });

This just checks if any are still being animated via the :animated selector. If you were animating lots of different things then use the same concept, just add to the selector like this:

$(".myclass:animated, .myClass2:animated")

If you're using it in lots of places, I'd make that callback a onFinish function or something to tidy it up.

Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • Nice, I like the definative aspect of this. – Mark Schultheiss Mar 12 '10 at 12:40
  • Ran into this problem too. Clever solution. – Nathan Osman Mar 13 '10 at 02:44
  • I've tried this, but the :animated selector doesn't seem to have cleared at the moment the callback is fired. Adding .not(this) before .length make it work. – grahamparks Jul 23 '10 at 11:29
  • @grahamparks - It won't be clear *if* there's another animation queued on the element, remember this is just the callback for *this* animation, another may be after it...this script was really written for the context of the question :) – Nick Craver Jul 23 '10 at 11:45
  • 2
    Nick: Other animations aren't the problem. In my testing, the element still matches *:animated at the moment its own callback is called. Thus length is never 0. – grahamparks Jul 26 '10 at 13:58
3

In my case I have to define

if ($(".myclass:animated").length === 1)