jQuery / .animate() — Crear efectos personalizados

.animate() cambia propiedades CSS actuales hasta sus objetivos con el tiempo. .animate({ left: 100, width: 160, opacity: 0.4 }) modifica posición, ancho y opacidad juntos. Duración y easing controlan el recorrido; la finalización se llama por elemento.

Propiedades y duración

El primer argumento .animate() contiene objetivos. left y width numéricos son píxeles aquí; opacity no tiene unidad y va de cero a uno. Un objeto cambia simultáneamente; llamadas separadas entran normalmente en la cola.

left necesita posición distinta de static. relative desplaza desde el origen, no a x=100 del documento. width mide contenido; content-box simple evita condiciones extra de borde y padding.

duration son milisegundos, 400 por defecto. Easing cambia velocidad: swing y linear llegan a los mismos objetivos. No suponga interpolación de cualquier color o transform por jQuery básico; revise propiedades numéricas admitidas.

Ejemplo completo con dos elementos

Guarde UTF-8 animate-demo.html y abra. Internet carga la versión completa oficial 4.0.0. Animación temporizada necesita effects, no Slim. No necesita servidor ni paquetes. Intermedios, objetivos, parada y repetición se comprobaron en Edge para Windows con 3.7.1 y 4.0.0.

Las cajas comparten objetivos. Start restaura valores iniciales. Reset conserva duración/easing y restaura cajas y contadores. Durante ejecución se bloquea otro inicio.

<!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>

Inicio: ancho 80, left relativo cero y opacidad uno. Redondear pantalla no limita el cálculo a enteros.

state=ready
completed=0; batches=0
alpha: left=0; width=80; opacity=1
beta: left=0; width=80; opacity=1
  1. Start con 600ms y swing: tres propiedades cambian juntas.
  2. Final: state=done, completed=2; batches=1 ; ambas left=100; width=160; opacity=0.4.
  3. Reset, cambie solo easing a linear y compare. Los objetivos coinciden.
  4. Con 1200ms, Stop here conserva intermedios en state=stopped, sin done posterior.
  5. Reset y repita. 0ms alcanza objetivos y contadores inmediatamente.
  6. Movimiento reducido también termina inmediatamente aunque elija 1200ms.

Final individual y colectivo

complete se llama dos veces y this es el nodo correspondiente. Un callback no es el final colectivo. .animate() no bloquea JavaScript hasta terminar.

.promise("fx") observa trabajo del conjunto. Aquí incrementa batches una vez y habilita Start. Tareas adicionales de la cola estándar pueden ampliar la espera.

Cola vacía no demuestra final natural. .stop(true, false) puede vaciar a mitad y resolver la promesa fx. Stop y Reset cambian generation primero para que callbacks antiguos no sustituyan cancelación por done.

Llamadas repetidas suelen acumular efectos; deshabilitar y busy lo evitan. Redirigir desde la posición actual necesita otra política explícita.

Parada, restauración y disposición

jumpToEnd=false conserva valores actuales. Reset establece las tres propiedades; intermedios antiguos cambiarían la comparación.

prefers-reduced-motion reduce usa cero en esta ejecución con mismo objetivo y final, sin cambiar configuración global.

Posición y ancho afectan alrededor y solapamientos: revise pantalla estrecha, zoom, texto largo y contenedores desplazables. Opacidad cero no implica display:none ni ocultación accesible.

Evite que transiciones CSS o plugins controlen las mismas propiedades a la vez. Defina responsabilidad de elementos, propiedades y colas, y limite selectores y eventos a la función.