La devolución de llamada de .animate () se llama dos veces jquery

103

Desde que agregué algo de scrollTopanimación, algunas partes de mi devolución de llamada se llaman dos veces:

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

Solo parece repetir el .show(), al menos no tengo la impresión de que el load()o el sean .fadeIn()llamados por segunda vez también. El .show()se repite tan pronto como ha terminado por primera vez. ¡Establecer la velocidad de animación scrollTop en 0no ayudó por cierto!

Supongo que tiene algo que ver con la cola de animación, pero no puedo encontrar una solución y, especialmente, por qué sucede esto.

Anónimo
fuente

Respuestas:

162

animatellama a su devolución de llamada una vez para cada elemento del conjunto al que llama animate:

Si se suministra, el start, step, progress, complete, done, fail, y alwaysdevoluciones de llamada se denominan en una base por elemento ...

Dado que está animando dos elementos (el htmlelemento y el bodyelemento), obtiene dos devoluciones de llamada. (Para cualquiera que se pregunte por qué el OP está animando dos elementos, es porque la animación funciona bodyen algunos navegadores pero htmlen otros navegadores).

Para obtener una única devolución de llamada cuando la animación esté completa, los animatedocumentos le indican que use el promisemétodo para obtener una promesa para la cola de animación, luego use thenpara poner en cola la devolución de llamada:

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

(Nota: Kevin B señaló esto en su respuesta cuando se hizo la pregunta por primera vez. No lo hice hasta cuatro años después, cuando noté que faltaba, lo agregué y ... luego vi la respuesta de Kevin. Por favor, dé su respuesta el amor se merece. Pensé que como esta es la respuesta aceptada, debería dejarla.)

Aquí hay un ejemplo que muestra tanto las devoluciones de llamada de elementos individuales como la devolución de llamada de finalización general:

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
fuente
2
Sí, esa fue la razón y, de hecho, no puedo recordar por qué escribí html, body. Es hora de volver a encender mi cerebro. Gracias
Anónimo
21
Probablemente porque animar solo html no funcionará en Webkit, y solo el cuerpo no funcionará en Opera. El uso de ambos garantizará que siempre funcione, pero activará la devolución de llamada dos veces en Firefox. (Puede que me equivoque en los navegadores ...)
Jon Gjengset
2
htmles necesario para IE, y la devolución de llamada se activará dos veces para la mayoría de los demás navegadores. Estoy tratando de encontrar una solución al mismo problema.
Simon Robb
9
Lo solucioné creando una bandera: se var ranOne = false; $('body,html').animate({ scrollTop: scrollTo }, scrollTime, 'swing', function () { if (ranOne) { ...action... ranOne = false; } else { ranOne = true; } }); siente hacky, pero tener que usar "body, html" es un poco hacky en primer lugar, entonces. (ECH pena por falta de saltos de línea, los comentarios de la conjetura no muestran ellos)
spinn
1
¡Increíble! Pasé horas tratando de hacer que mi animación de desplazamiento funcionara correctamente con $ ("html, body") para que no se disparara dos veces, ¡y esta respuesta es lo que me salvó! ¡Gracias!
Vasily Hall
172

Para obtener una única devolución de llamada para completar animaciones de varios elementos, use objetos diferidos.

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

Consulte la API de jQuery para obtener una descripción detallada de la promesa y los objetos diferidos .

Kevin B
fuente
Este es un problema resuelto sobre lo que queremos hacer al terminar la animación, pero el problema es de dos llamadas y obtuve +1 para eso, ¡pero no sé si el proceso animado se llama dos veces o no!
QMaster
1
El proceso animado no se llama dos veces, la devolución de llamada se llama una vez para cada elemento seleccionado. Por lo tanto, si selecciona 8 elementos de la lista y los anima a la izquierda, la devolución de llamada se ejecutará 8 veces. Mi solución le brinda una única devolución de llamada para cuando los 8 hayan terminado.
Kevin B
@KevinB, buena solución, y también me ayudó a resolver mis propios problemas de devolución de llamada. Gracias.
Lislis
Pensé que esta era una respuesta intrigante, pero desafortunadamente tiene consecuencias no deseadas. La promesa no solo retrasa la ejecución de la devolución de llamada hasta que los elementos del conjunto jQuery hayan terminado la animación actual (lo cual sería genial). Se resuelve solo después de que se hayan realizado todas las animaciones pendientes del conjunto. Entonces, si se configura una segunda animación mientras la primera aún se está ejecutando, la devolución de llamada destinada a la primera animación no se ejecuta hasta que finaliza la segunda animación. Vea este contenedor .
hashchange
1
Eso es correcto, espera a que se completen todas las animaciones actuales para los elementos seleccionados. No es lo suficientemente inteligente esperar solo a los que comenzaron en esta cadena (ni debería serlo)
Kevin B