O retorno de chamada de .animate () é chamado duas vezes jquery

103

Como eu adicionei alguma scrollTopanimação, algumas partes do meu retorno de chamada são chamadas duas vezes:

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

Parece apenas repetir o .show(), pelo menos não tenho a impressão de que o load()ou também .fadeIn()seja chamado uma segunda vez. O .show()é repetido assim que termina pela primeira vez. A propósito, definir a velocidade da animação scrollTop para 0não ajudou!

Presumo que tenha algo a ver com a fila de animação, mas não consigo descobrir como encontrar uma solução alternativa e, especialmente, por que isso está acontecendo.

Anônimo
fonte

Respostas:

162

animatechama seu callback uma vez para cada elemento no conjunto que você chama animate:

Se fornecido, o start, step, progress, complete, done, fail, e alwayschamadas de retorno são chamados em uma base per-elemento ...

Como você está animando dois elementos (o htmlelemento e o bodyelemento), você obtém dois retornos de chamada. (Para quem está se perguntando por que o OP está animando dois elementos, é porque a animação funciona bodyem alguns navegadores, mas htmlem outros.)

Para obter um único retorno de chamada quando a animação estiver concluída, os animatedocumentos indicam o uso do promisemétodo para obter uma promessa para a fila de animação e, em seguida, usar thenpara enfileirar o retorno de chamada:

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

(Observação: Kevin B apontou isso em sua resposta quando a pergunta foi feita pela primeira vez. Só fiz isso quatro anos depois, quando percebi que estava faltando, adicionei e ... então vi a resposta de Kevin. Dê sua resposta amor que merece. Achei que esta é a resposta aceita, deveria deixá-la como está.)

Aqui está um exemplo que mostra os retornos de chamada de elemento individual e o retorno de chamada de conclusão geral:

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
fonte
2
Sim, essa foi a razão e na verdade não me lembro porque escrevi html, body. É hora de ligar meu cérebro novamente. Obrigado
Anônimo
21
Provavelmente porque a animação apenas em html não funcionará no Webkit e apenas o corpo não funcionará no Opera. Usar ambos garantirá que sempre funcione, mas acionará o retorno de chamada duas vezes no Firefox. (Posso ter errado os navegadores ...)
Jon Gjengset
2
htmlé necessário para o IE e o retorno de chamada será acionado duas vezes para a maioria dos outros navegadores. Estou tentando encontrar uma solução para o mesmo problema.
Simon Robb
9
Lidei com isso criando um sinalizador: var ranOne = false; $('body,html').animate({ scrollTop: scrollTo }, scrollTime, 'swing', function () { if (ranOne) { ...action... ranOne = false; } else { ranOne = true; } }); Parece hacky, mas ter que usar "body, html" é meio hacky em primeiro lugar, então. (ech desculpe pela falta de quebras de linha, acho que os comentários não as mostram)
spinn
1
Inacreditável! Passei horas tentando fazer minha animação de rolagem funcionar corretamente com $ ("html, body") para que ela não disparasse duas vezes, e essa resposta foi o que me salvou! Obrigado!
Vasily Hall
172

Para obter um único retorno de chamada para a conclusão de animações de vários elementos, use objetos adiados.

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

Consulte a API jQuery para obter uma descrição detalhada de Promise and Deferred Objects .

Kevin B
fonte
Este é o problema resolvido sobre o que queremos fazer ao terminar a animação, mas problema cerca de duas vezes, ligue e ganhe +1 para isso, mas eu não sei se o processo de animação é chamado duas vezes ou não ?!
QMaster
1
O processo animado não é chamado duas vezes, o retorno de chamada é chamado uma vez para cada elemento selecionado. Portanto, se você selecionar 8 itens da lista e animá-los à esquerda, o retorno de chamada será executado 8 vezes. Minha solução fornece um único retorno de chamada para quando todos os 8 forem concluídos.
Kevin B
@KevinB, boa solução, e também ajudou a resolver meus próprios problemas de callback. Obrigado.
lislis
Achei essa resposta intrigante, mas infelizmente teve consequências inesperadas. A promessa não apenas atrasa a execução do retorno de chamada até que os elementos do conjunto jQuery tenham terminado a animação atual (o que seria ótimo). Ele é resolvido somente depois que todas as animações pendentes do conjunto são feitas. Portanto, se uma segunda animação for configurada enquanto a primeira ainda está em execução, o retorno de chamada pretendido para a primeira animação não será executado até que a segunda animação termine. Veja esta caixa .
hashchange de
1
Isso está correto, ele espera que todas as animações atuais sejam concluídas para os elementos selecionados. Não é inteligente o suficiente esperar apenas pelos iniciados nesta cadeia (nem deveria ser)
Kevin B