jQuery / .animate() — Criar efeitos personalizados

.animate() muda propriedades CSS atuais até os alvos ao longo do tempo. .animate({ left: 100, width: 160, opacity: 0.4 }) modifica posição, largura e opacidade juntas. Duração e easing controlam o percurso; a conclusão é chamada por elemento.

Propriedades e duração

O primeiro argumento .animate() contém alvos. left e width numéricos são pixels aqui; opacity não tem unidade e vai de zero a um. Um objeto muda simultaneamente; chamadas separadas entram normalmente na fila.

left precisa de posição diferente de static. relative desloca desde a origem, não para x=100 do documento. width mede conteúdo; content-box simples evita condições extras de borda e padding.

duration é em milissegundos, padrão 400. Easing muda velocidade: swing e linear atingem os mesmos alvos. Não suponha interpolação de qualquer cor ou transform pelo jQuery básico; confira propriedades numéricas aceitas.

Exemplo completo com dois elementos

Salve UTF-8 animate-demo.html e abra. A Internet carrega a versão completa oficial 4.0.0. Animação temporizada precisa de effects, não Slim. Não precisa de servidor nem pacotes. Intermediários, alvos, parada e repetição foram conferidos no Edge para Windows com 3.7.1 e 4.0.0.

As caixas compartilham alvos. Start restaura valores iniciais. Reset mantém duração/easing e restaura caixas e contadores. Durante execução outro início é bloqueado.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Animate CSS properties and observe completion</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 20px; }
    label { display: block; margin: 8px 0; }
    select, button { font: inherit; margin: 4px; }
    #stage { background: #eef4f4; padding: 10px; overflow-x: auto; }
    .box { position: relative; left: 0; width: 80px; height: 45px; margin: 12px 0; background: teal; color: white; box-sizing: content-box; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <label>Duration <select id="duration"><option value="600">600ms</option><option value="1200">1200ms</option><option value="0">0ms</option></select></label>
  <label>Easing <select id="easing"><option value="swing">swing</option><option value="linear">linear</option></select></label>
  <button type="button" id="start">Start</button>
  <button type="button" id="stop">Stop here</button>
  <button type="button" id="reset">Reset</button>
  <div id="stage"><div class="box" id="alpha">Alpha</div><div class="box" id="beta">Beta</div></div>
  <pre id="result" aria-live="polite"></pre>
  <script>
    jQuery(function ($) {
      const $boxes = $("#stage > .box");
      let generation = 0, busy = false, state = "ready", completed = 0, batches = 0;
      function report() {
        const values = $boxes.map(function () {
          const $box = $(this);
          return this.id + ": left=" + Math.round(parseFloat($box.css("left"))) + "; width=" + Math.round($box.width()) + "; opacity=" + Number(parseFloat($box.css("opacity")).toFixed(2));
        }).get();
        $("#result").text(["state=" + state, "completed=" + completed + "; batches=" + batches, ...values].join("\n"));
      }
      function reset() {
        generation++; $boxes.stop(true, false).css({ left: 0, width: 80, opacity: 1 });
        busy = false; state = "ready"; completed = batches = 0;
        $("#start").prop("disabled", false); report();
      }
      $("#start").on("click", function () {
        if (busy) return;
        reset(); busy = true; state = "running"; $(this).prop("disabled", true);
        const run = generation;
        const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
        const duration = reduce ? 0 : Number($("#duration").val());
        $boxes.animate({ left: 100, width: 160, opacity: 0.4 }, {
          duration: duration, easing: $("#easing").val(),
          complete: function () { if (run === generation) { completed++; report(); } }
        });
        $boxes.promise("fx").done(function () {
          if (run !== generation) return;
          batches++; busy = false; state = "done";
          $("#start").prop("disabled", false); report();
        });
        report();
      });
      $("#stop").on("click", function () {
        if (!busy) return;
        generation++; $boxes.stop(true, false);
        busy = false; state = "stopped"; $("#start").prop("disabled", false); report();
      });
      $("#reset").on("click", reset);
      reset();
    });
  </script>
</body>
</html>

Início: largura 80, left relativo zero e opacidade um. Arredondar exibição não limita o cálculo a inteiros.

state=ready
completed=0; batches=0
alpha: left=0; width=80; opacity=1
beta: left=0; width=80; opacity=1
  1. Start com 600ms e swing: três propriedades mudam juntas.
  2. Final: state=done, completed=2; batches=1 ; ambas left=100; width=160; opacity=0.4.
  3. Reset, mude só easing para linear e compare. Os alvos são iguais.
  4. Com 1200ms, Stop here mantém intermediários em state=stopped, sem done posterior.
  5. Reset e repita. 0ms alcança alvos e contadores imediatamente.
  6. Movimento reduzido também conclui imediatamente apesar de 1200ms escolhido.

Conclusão individual e coletiva

complete é chamado duas vezes e this é o nó correspondente. Um callback não conclui o conjunto. .animate() não bloqueia JavaScript até terminar.

.promise("fx") observa trabalho do conjunto. Aqui incrementa batches uma vez e habilita Start. Tarefas extras da fila padrão podem ampliar a espera.

Fila vazia não comprova conclusão natural. .stop(true, false) pode esvaziar no meio e resolver a promessa fx. Stop e Reset mudam generation primeiro para callbacks antigos não substituírem cancelamento por done.

Chamadas repetidas costumam acumular efeitos; desabilitar e busy evitam isso. Redirecionar da posição atual exige outra política explícita.

Parada, restauração e layout

jumpToEnd=false mantém valores atuais. Reset define três propriedades; intermediários antigos mudariam a comparação.

prefers-reduced-motion reduce usa zero nesta execução com mesmo alvo e conclusão, sem mudar configuração global.

Posição e largura afetam arredores e sobreposição: confira tela estreita, zoom, texto longo e contêineres de rolagem. Opacidade zero não implica display:none nem ocultação acessível.

Evite transições CSS ou plugins controlando as mesmas propriedades ao mesmo tempo. Defina responsabilidade de elementos, propriedades e filas e limite seletores e eventos à função.