Le rappel de .animate () est appelé deux fois jquery

103

Depuis que j'ai ajouté quelques scrollTop-animation, certaines parties de mon callback sont appelées deux fois:

$('html, body').animate({scrollTop: '0px'}, 300,function() {
    $('#content').load(window.location.href, postdata, function() {                 
        $('#step2').addClass('stepactive').hide().fadeIn(700, function() {
            $('#content').show('slide',800);                    
        });
    });
});

Il semble seulement répéter le .show(), du moins je n'ai pas l'impression que le load()ou le .fadeIn()soit appelé une deuxième fois aussi. Le .show()se répète dès qu'il est terminé pour la première fois. Régler la vitesse d'animation scrollTop sur 0n'a pas aidé d'ailleurs!

Je suppose que cela a quelque chose à voir avec la file d'attente d'animation, mais je ne peux pas comprendre comment trouver une solution de contournement et surtout pourquoi cela se produit.

Anonyme
la source

Réponses:

162

animateappelle son callback une fois pour chaque élément de l'ensemble que vous appelez animate:

Si elle est fournie, le start, step, progress, complete, done, failet alwayscallbacks sont appelés sur une base par élément ...

Puisque vous animez deux éléments (l' htmlélément et l' bodyélément), vous obtenez deux rappels. (Pour quiconque se demande pourquoi l'OP anime deux éléments, c'est parce que l'animation fonctionne bodysur certains navigateurs mais sur htmld'autres navigateurs.)

Pour obtenir un seul rappel lorsque l'animation est terminée, la animatedocumentation vous indique d'utiliser la promiseméthode pour obtenir une promesse pour la file d'attente d'animation, puis d'utiliser thenpour mettre en file d'attente le rappel:

$("html, body").animate(/*...*/)
    .promise().then(function() {
        // Animation complete
    });

(Remarque: Kevin B l'a souligné dans sa réponse lorsque la question a été posée pour la première fois. Je ne l'ai fait que quatre ans plus tard, lorsque j'ai remarqué qu'elle manquait, je l'ai ajoutée et ... j'ai ensuite vu la réponse de Kevin. Veuillez donner sa réponse. l'amour qu'il mérite. Je me suis dit que c'était la réponse acceptée, je devrais la laisser.)

Voici un exemple montrant à la fois les rappels d'élément individuels et le rappel d'achèvement global:

jQuery(function($) {

  $("#one, #two").animate({
    marginLeft: "30em"
  }, function() {
    // Called per element
    display("Done animating " + this.id);
  }).promise().then(function() {
    // Called when the animation in total is complete
    display("Done with animation");
  });

  function display(msg) {
    $("<p>").html(msg).appendTo(document.body);
  }
});
<div id="one">I'm one</div>
<div id="two">I'm two</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

TJ Crowder
la source
2
Ouais, c'était la raison et en fait je ne me souviens pas pourquoi j'ai écrit html, body. Il est temps de rallumer mon cerveau. Merci
Anonyme
21
Probablement parce qu'animer uniquement du HTML ne fonctionnera pas dans Webkit, et que le corps ne fonctionnera pas dans Opera. L'utilisation des deux garantira que cela fonctionne toujours, mais déclenchera le rappel deux fois dans Firefox. (Je me suis peut-être trompé dans les navigateurs ...)
Jon Gjengset
2
htmlest requis pour IE, et le rappel se déclenchera deux fois pour la plupart des autres navigateurs. J'essaye de trouver une solution au même problème.
Simon Robb
9
Je l'ai traité en créant un drapeau: var ranOne = false; $('body,html').animate({ scrollTop: scrollTo }, scrollTime, 'swing', function () { if (ranOne) { ...action... ranOne = false; } else { ranOne = true; } }); cela semble piraté, mais devoir utiliser "body, html" est une sorte de piratage en premier lieu, donc. (ech désolé pour le manque de sauts de ligne, les commentaires guess ne les montrent pas)
Spinn
1
Incroyable! J'ai passé des heures à essayer de faire fonctionner correctement mon animation de défilement avec $ ("html, body") pour qu'elle ne se déclenche pas deux fois, et c'est cette réponse qui m'a sauvé! Merci!
Vasily Hall
172

Pour obtenir un seul rappel pour l'achèvement de plusieurs animations d'éléments, utilisez des objets différés.

$(".myClass").animate({
  marginLeft: "30em"
}).promise().done(function(){
  alert("Done animating");
});

Voir l'API jQuery pour une description détaillée de la promesse et des objets différés .

Kevin B
la source
C'est résolu le problème de ce que nous voulons faire une fois l'animation terminée, mais le problème est d'appeler deux fois et d'obtenir +1 pour cela, mais je ne sais pas si le processus d'animation est appelé deux fois ou non?!
QMaster
1
Le processus d'animation n'est pas appelé deux fois, le rappel est appelé une fois pour chaque élément sélectionné. Ainsi, si vous sélectionnez 8 éléments de la liste et que vous les animez vers la gauche, le rappel sera exécuté 8 fois. Ma solution vous donne un seul rappel lorsque les 8 sont terminés.
Kevin B
@KevinB, une bonne solution, et cela a également aidé à résoudre mes propres problèmes de rappel. Merci.
lislis
Je pensais que c'était une réponse intéressante, mais malheureusement, elle a des conséquences imprévues. La promesse ne retarde pas seulement l'exécution du rappel jusqu'à ce que les éléments de l'ensemble jQuery aient terminé l'animation actuelle (ce qui serait génial). Il n'est résolu qu'après que toutes les animations en attente de l'ensemble sont terminées. Ainsi, si une seconde animation est configurée alors que la première est toujours en cours d'exécution, le rappel destiné à la première animation ne s'exécute qu'après la fin de la seconde animation. Voir ce bac .
hashchange
1
C'est correct, il attend que toutes les animations en cours se terminent pour les éléments sélectionnés. Il n'est pas assez intelligent d'attendre seulement ceux qui ont commencé dans cette chaîne (et cela ne devrait pas l'être)
Kevin B