wandres.dev
LOTTIE Y AFTER EFFECTS · La animación del diseñador

dotLottie y los reproductores: qué mejora y qué no

El contenedor .lottie y sus ventajas reales frente a un JSON servido con brotli, el mapa de reproductores en 2026, la elección entre SVG y canvas, y cómo controlar la reproducción sin desperdiciar fotogramas.

⏱ 20 min

dotLottie se presenta como la evolución del formato, y buena parte de lo que se dice sobre él —que comprime diez veces— es cierto y a la vez irrelevante, porque tu servidor ya comprimía el JSON. Las ventajas reales están en otro sitio: en el empaquetado de varias animaciones con sus recursos, y sobre todo en la generación de reproductores construidos sobre un motor vectorial en WebAssembly en lugar de sobre el DOM. Esa segunda parte sí cambia el coste.

🎯 Al terminar esta lección sabrás
  • Describir la estructura interna de un fichero .lottie y sus ventajas frente a un .json.
  • Situar los reproductores disponibles y qué motor usa cada uno.
  • Elegir entre renderizador de SVG y de canvas con un criterio medible.
  • Controlar la reproducción y evitar que una animación fuera de pantalla consuma fotogramas.

El contenedor

Un .lottie es un ZIP con compresión Deflate y una estructura convenida dentro:

unzip -l animacion.lottie
manifest.json
animations/carga.json
animations/exito.json
images/logo_0.png
themes/oscuro.json

manifest.json describe qué hay dentro: el array de animaciones con sus identificadores, los temas disponibles y, en la versión 2 de la especificación, las máquinas de estado que definen transiciones entre animaciones según eventos.

Las ventajas reales, ordenadas de mayor a menor:

Un solo fichero con sus recursos. Si la animación usa imágenes, el .json con base64 las infla un tercio y el .json sin base64 te obliga a desplegar una carpeta con rutas correctas. El ZIP resuelve las dos cosas.

Varias animaciones en un artefacto. Estados de un mismo componente —carga, éxito, error— que se despliegan y versionan juntos.

Temas. Sustituir colores sin duplicar el fichero, que es el caso de “la misma animación en modo claro y oscuro” resuelto sin exportar dos veces desde After Effects.

Máquinas de estado. Definir en el propio artefacto que al terminar la animación de carga se pase a la de éxito y que al pasar el ratón se reproduzca otra. Es la parte más nueva y la que menos soporte tiene fuera de los reproductores de LottieFiles.

Y la ventaja que se anuncia primero y vale menos:

⚠️
La compresión de dotLottie se solapa con la de tu servidor

El argumento de “hasta diez veces más pequeño” compara el ZIP con el JSON sin comprimir. Un JSON de Lottie es texto con muchísima repetición y brotli lo reduce en proporciones parecidas. Si sirves con Content-Encoding: br, la mejora de pasar a .lottie en bytes transferidos es pequeña. Mídelo antes de reescribir el pipeline por esa razón: curl -sH 'Accept-Encoding: br' -o /dev/null -w '%{size_download}\n' https://tu-dominio/animacion.json.

Los reproductores

Paquete Motor Salida Notas
lottie-web JavaScript SVG, canvas, HTML El original de Airbnb, el más compatible
lottie-web build light JavaScript SVG, canvas Sin soporte de expresiones, notablemente más pequeño
@lottiefiles/dotlottie-web ThorVG en WebAssembly Canvas Backends de software, WebGL2 y WebGPU experimental
@lottiefiles/dotlottie-wc El anterior Canvas Web component, funciona en cualquier framework
lottie-react, @lottiefiles/dotlottie-react Los anteriores Envoltorios de React

La diferencia arquitectónica es la que importa. lottie-web es un intérprete escrito en JavaScript que evalúa el grafo y manipula el DOM: en modo SVG crea y actualiza atributos de elementos path, g y clipPath en cada fotograma. @lottiefiles/dotlottie-web empaqueta ThorVG —un motor de gráficos vectoriales en C++, del orden de ciento cincuenta kilobytes compilado a WebAssembly— y dibuja directamente sobre un canvas. No toca el DOM en ningún fotograma.

Uso mínimo de cada uno:

// lottie-web
import lottie from "lottie-web";

const animacion = lottie.loadAnimation({
  container: document.querySelector("#caja"),
  renderer: "svg",
  loop: true,
  autoplay: false,
  path: "/animaciones/carga.json",
});

animacion.addEventListener("DOMLoaded", () => animacion.play());
// dotlottie-web
import { DotLottie } from "@lottiefiles/dotlottie-web";

const dl = new DotLottie({
  canvas: document.querySelector("#lienzo"),
  src: "/animaciones/carga.lottie",
  loop: true,
  autoplay: true,
});

dl.addEventListener("complete", () => dl.destroy());
<!-- Web component, sin bundler -->
<script type="module" src="https://unpkg.com/@lottiefiles/dotlottie-wc@0.7.2/dist/index.js"></script>
<dotlottie-wc src="/animaciones/carga.lottie" autoplay loop style="width:160px;height:160px"></dotlottie-wc>

Los reproductores web component son cómodos y tienen un coste que conviene ver: cargan el motor completo aunque tu animación sea trivial, y si pones tres en la página cargan el motor una vez pero crean tres instancias con su propio bucle.

SVG o canvas

La elección de renderizador es la decisión de rendimiento más importante que vas a tomar con Lottie, y la respuesta no es la misma en todos los casos.

El renderizador de SVG crea un nodo del DOM por cada forma y actualiza sus atributos en cada fotograma. A favor: escala sin perder nitidez a cualquier densidad de píxel sin que hagas nada, los trazos finos se ven perfectos, y puedes inspeccionar el resultado en el árbol de elementos, lo cual es utilísimo para depurar. En contra: cada fotograma es una tanda de mutaciones del DOM que dispara recálculo de estilo y repintado de un área potencialmente grande, y el coste crece de forma aproximadamente lineal con el número de formas. Con máscaras y mattes, además, el motor de SVG tiene que componer capas, que es de lo más caro que sabe hacer.

El renderizador de canvas dibuja en un mapa de bits. A favor: un solo nodo, cero mutaciones del DOM, y el coste depende del área dibujada más que del número de formas. En contra: tienes que gestionar tú la densidad de píxel —un canvas a devicePixelRatio 3 tiene nueve veces más píxeles que rasterizar— y una animación a pantalla completa en un móvil de gama alta puede ser más cara en canvas que en SVG por pura cantidad de píxeles.

La regla que funciona: muchas formas en poca área, canvas; pocas formas en mucha área, SVG. Y para el caso de dotLottie con ThorVG no hay elección: siempre es canvas, pero el rasterizado ocurre en código nativo compilado en lugar de en el motor de SVG del navegador, lo que cambia bastante la constante.

Al usar canvas con lottie-web, hay dos ajustes que casi nadie pone y que valen mucho:

const animacion = lottie.loadAnimation({
  container: document.querySelector("#caja"),
  renderer: "canvas",
  loop: true,
  autoplay: true,
  path: "/animaciones/carga.json",
  rendererSettings: {
    // Limita la densidad: en moviles de gama alta ahorra hasta un 80% de pixeles.
    dpr: Math.min(window.devicePixelRatio, 2),
    clearCanvas: true,
    preserveAspectRatio: "xMidYMid meet",
  },
});

// Reduce la calidad de las curvas y los degradados. Notable en animaciones
// pequenas, invisible por debajo de 200 pixeles de lado.
lottie.setQuality("low");

No desperdiciar fotogramas

El error más frecuente en producción con Lottie no es de renderizador: es reproducir animaciones que nadie está mirando. Un bucle infinito en el pie de página consume su coste completo mientras el usuario lee la cabecera, y si hay tres, tres veces.

function reproducirSoloVisible(elemento, animacion) {
  const observador = new IntersectionObserver(
    ([entrada]) => (entrada.isIntersecting ? animacion.play() : animacion.pause()),
    { threshold: 0.01 }
  );
  observador.observe(elemento);
  return () => observador.disconnect();
}

Tres cosas más que hay que hacer siempre y casi nunca se hacen.

Parar cuando la pestaña no está visible. El navegador ya limita requestAnimationFrame en pestañas ocultas, pero no en todos los casos ni en todos los motores, y si el reproductor usa temporizadores propios no lo limita nada.

document.addEventListener("visibilitychange", () => {
  document.hidden ? animacion.pause() : animacion.play();
});

Destruir al desmontar. Un reproductor que sobrevive a su contenedor sigue evaluando el grafo contra un nodo huérfano. animacion.destroy() en lottie-web, dl.destroy() en dotLottie.

Cargar el reproductor bajo demanda. El motor pesa mucho más que la animación. Si la pieza no está en el primer pintado, no tiene por qué estar en el bundle inicial:

const caja = document.querySelector("#caja");
new IntersectionObserver(async ([e], obs) => {
  if (!e.isIntersecting) return;
  obs.disconnect();
  const { default: lottie } = await import("lottie-web/build/player/lottie_light");
  lottie.loadAnimation({ container: caja, renderer: "svg", loop: true, autoplay: true, path: "/a.json" });
}, { rootMargin: "300px" }).observe(caja);

Y respeta la preferencia de movimiento reducido. Una ilustración animada en bucle es exactamente el tipo de movimiento periférico continuo que la preferencia pretende evitar:

const sinMovimiento = window.matchMedia("(prefers-reduced-motion: reduce)");
if (sinMovimiento.matches) {
  animacion.goToAndStop(Math.floor(animacion.totalFrames * 0.5), true);
} else {
  animacion.play();
}

Detener en un fotograma representativo, no en el primero, es lo que hace que la versión reducida siga comunicando algo. El primer fotograma de una animación suele ser un lienzo casi vacío.

ThorVG cambia la ecuación porque saca el rasterizado del hilo del DOM

Durante casi una década el debate sobre el coste de Lottie fue un debate sobre el DOM, porque el único reproductor serio era un intérprete de JavaScript que actualizaba atributos de SVG. Con ese diseño, cada fotograma de la animación es una tanda de mutaciones que entra en el mismo camino crítico que el resto de tu página: recálculo de estilo, invalidación, repintado. Da igual lo optimizado que esté el intérprete, porque el trabajo caro no lo hace él sino el navegador reaccionando a él, y ese trabajo compite directamente con el de tu interfaz. Un motor vectorial compilado a WebAssembly que escribe píxeles en un canvas rompe esa relación: el fotograma se compone entero fuera del DOM y el navegador solo ve que un mapa de bits cambió, que es la operación más barata que existe. La diferencia no es de porcentajes, es de naturaleza del trabajo, y se nota especialmente en el caso peor —muchas formas pequeñas con mattes— que es justo donde el renderizador de SVG se derrumba. Ahora la parte honesta, porque el titular se vende demasiado alegre. Lo que se elimina es el coste del DOM, no el coste de interpretar el grafo ni el de rasterizar. El intérprete sigue teniendo que evaluar todas las propiedades animadas de todas las capas en cada fotograma, y el rasterizador sigue teniendo que rellenar píxeles, y esos dos costes escalan con la complejidad del fichero y con el área exactamente igual que antes. Cambiar de reproductor no arregla una animación mal hecha: convierte un problema de mutaciones del DOM en un problema de cómputo puro, que es más fácil de razonar y de acotar, pero sigue siendo tuyo. Y añade un coste nuevo que antes no existía: descargar, compilar e instanciar un módulo WebAssembly antes del primer fotograma, lo que puede ser peor que el reproductor de JavaScript si tu animación es un icono de doce kilobytes. La regla que sale de aquí es de escala: por debajo de cierto tamaño de animación el coste fijo del motor nativo domina y no compensa; por encima, no hay comparación.