wandres.dev
GSAP X · Plugins de SVG

DrawSVG: dibujar y borrar trazos

Los formatos del valor drawSVG, la diferencia entre animar hacia un estado y animar un recorrido, el sufijo live, y los bugs de navegador documentados.

⏱ 17 min

El efecto de un trazo que se dibuja solo se puede hacer a mano con stroke-dasharray y stroke-dashoffset, y la mayoría de la gente lo hace así una vez, se pelea con el cálculo de la longitud del trazo, descubre que hay que medirla con JavaScript y que cambia con el tamaño, y no lo vuelve a hacer. DrawSVG automatiza exactamente esa parte tediosa y añade una capacidad que la técnica manual no tiene: controlar los dos extremos del trazo visible de forma independiente, lo que abre la puerta a segmentos que se desplazan a lo largo del contorno.

🎯 Al terminar esta lección sabrás
  • Escribir los valores de drawSVG en todas sus formas, incluida la de dos posiciones.
  • Distinguir entre animar hacia un estado y animar un recorrido con fromTo.
  • Usar el sufijo live para trazos que cambian de longitud.
  • Reconocer los bugs de navegador documentados y aplicar los rodeos.

El valor es un estado, no un recorrido

Este es el malentendido que hay que despejar primero. drawSVG: "20% 80%" no significa “dibuja desde el 20% hasta el 80%”. Significa “termina con el trazo visible entre el 20% y el 80%”, y GSAP animará desde el estado actual hasta ese estado.

Los formatos aceptados son estos:

Valor Resultado
"0 100%" Todo el trazo visible. Equivale a true
"100%" Igual que el anterior: un solo valor asume 0 como inicio
0 Nada visible. El trazo desaparece
"20% 80%" Solo el tramo central; huecos en ambos extremos
"50% 50%" Un punto en el centro, sin longitud
"0 200px" Los primeros 200 píxeles de longitud del trazo

Se pueden usar porcentajes o longitudes absolutas indistintamente. Y de aquí sale la forma canónica del efecto de dibujado, que es un from:

import { gsap } from "gsap";
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin";

gsap.registerPlugin(DrawSVGPlugin);

// Dibujar desde nada hasta el trazo completo
gsap.from("#firma path", {
  drawSVG: 0,
  duration: 1.6,
  ease: "power1.inOut",
  stagger: 0.15,
});

Con from, el estado de partida es “nada visible” y el de llegada el actual, que es el trazo completo. Es más robusto que un fromTo porque no impone el estado final: si mañana el trazo cambia, sigue funcionando.

Para el efecto de un segmento que recorre el contorno sin cambiar de longitud, sí hace falta fromTo, y es la capacidad que la técnica manual no da:

// Un destello corto que viaja por todo el contorno
gsap.fromTo(
  "#circuito",
  { drawSVG: "0% 5%" },
  { drawSVG: "95% 100%", duration: 2, ease: "none", repeat: -1 }
);

Y para el efecto de un trazo que crece desde el centro hacia ambos lados:

gsap.fromTo("#linea", { drawSVG: "50% 50%" }, { drawSVG: "0% 100%", duration: 1 });

Qué elementos acepta y qué hace por debajo

La documentación lista path, line, polyline, polygon, rect y ellipse. El método estático DrawSVGPlugin.getLength(), que devuelve la longitud del trazo de un elemento, admite además circle.

Lo que hace el plugin es calcular la longitud total del trazo y manipular stroke-dasharray y stroke-dashoffset para dejar visible solo el tramo que pides. No toca el fill en absoluto: un elemento sin trazo definido no mostrará nada aunque animes su drawSVG.

<!-- Sin stroke y stroke-width no hay nada que dibujar -->
<path id="firma" d="M10,80 C40,10 65,10 95,80 S150,150 180,80"
      fill="none" stroke="#89b4fa" stroke-width="4" stroke-linecap="round" />

Los dos métodos estáticos son estos:

DrawSVGPlugin.getLength("#firma");    // longitud total del trazo
DrawSVGPlugin.getPosition("#firma");  // posicion actual del DrawSVG
💡
Un solo trazo por elemento

La documentación recomienda usar DrawSVG sobre paths de un solo subcontorno, es decir, sin varios comandos M. Con varios subcontornos, el navegador aplica el patrón de guiones de forma continua a través de todos ellos, y el resultado suele ser que el trazo salta de una pieza a otra en lugar de dibujarse pieza a pieza. La solución es dividir el path en un elemento por subcontorno y escalonar el dibujado con un stagger.

El sufijo live

Hay un caso que rompe la técnica: un trazo cuya longitud cambia después de que el tween haya empezado. Ocurre cuando el SVG es responsive y se escala con el contenedor, o cuando animas el propio atributo d a la vez.

El plugin mide la longitud una vez y calcula los guiones a partir de ella. Si la longitud cambia, los porcentajes dejan de corresponder. Añadiendo la palabra live al final del valor, la longitud se vuelve a medir en cada tick.

gsap.to("#trazo", { drawSVG: "20% 70% live", duration: 2 });

Cuesta una medición por frame, así que solo se usa cuando de verdad hace falta.

Los bugs documentados

La documentación de GSAP recoge dos problemas de navegador que conviene conocer antes de perder una tarde buscándolos en tu código.

Firefox calcula mal la longitud total de algunos path. El síntoma es que el trazo se queda ligeramente corto y nunca llega a cerrar el cien por cien. Los rodeos son dos: añadir más anclas al path en el editor de vectores, o sobrepasarse en el valor final, poniendo "0 102%" en lugar de "0 100%".

Safari en iOS renderiza mal los trazos de rect. Aparecen artefactos en las esquinas, el trazo se ve más grueso de lo que debería y el punto de partida está desplazado. La solución es no usar rect: conviértelo a path o a polyline.

Y un tercero que no es un bug sino una limitación: el contenido de un elemento use no se puede afectar. El tween se ejecuta sin errores y no se ve nada.

Frente a hacerlo a mano

Merece la pena saber qué te ahorras, porque el equivalente manual no es largo pero sí es frágil.

// Version manual con dasharray. Funciona, pero...
const trazo = document.querySelector("#firma");
const longitud = trazo.getTotalLength();

trazo.style.strokeDasharray = longitud;
trazo.style.strokeDashoffset = longitud;

gsap.to(trazo, { strokeDashoffset: 0, duration: 1.6, ease: "power1.inOut" });

Lo que falta en esa versión: no puedes controlar los dos extremos por separado, así que el segmento que recorre el contorno no se puede hacer; la longitud se mide una vez y no hay equivalente de live; hay que repetir el bloque de medición por cada elemento en lugar de pasar un selector con varios; y getTotalLength() solo existe en path, line, polyline y polygon, no en rect ni en ellipse.

Para un caso único y sencillo, la versión manual es perfectamente razonable y no añade dependencias. Para cualquier cosa con varios trazos o con extremos independientes, el plugin gana claramente.

DrawSVG no dibuja: revela un trazo que siempre estuvo entero, y esa distinción cambia lo que puedes hacer con él

Hay una intuición equivocada que limita a mucha gente sin que se dé cuenta: pensar que el plugin va “creando” el trazo, píxel a píxel, como si hubiera un lápiz avanzando. Lo que ocurre es lo contrario. El trazo está entero desde el primer frame, completamente rasterizado, y lo que se anima es una máscara de guiones que decide qué parte de él se muestra. Es exactamente el mismo mecanismo que el enmascarado de líneas de texto que viste en el nivel anterior, aplicado a lo largo de una curva en lugar de a lo largo de un eje: revelación por oclusión, no por creación. Reconocer eso desbloquea de golpe todo un catálogo de efectos que no se te ocurrirían con la intuición del lápiz. Si lo que animas es una ventana sobre un trazo completo, entonces esa ventana puede moverse sin cambiar de tamaño —el destello que recorre un circuito—, puede tener dos ventanas si pones dos elementos superpuestos con el mismo d, puede ir hacia atrás, puede abrirse desde el centro, y puede estar en un sitio del contorno mientras otro elemento idéntico tiene la suya en otro. También explica por qué el rendimiento es el que es: no hay ningún cálculo geométrico por frame más allá de actualizar dos números en el patrón de guiones, y el navegador ya sabe rasterizar guiones muy rápido porque lleva haciéndolo desde antes de que existiera SVG. Y explica la limitación más molesta, la de los paths con varios subcontornos: el patrón de guiones es una propiedad del elemento entero y se aplica continuamente a lo largo de toda su longitud acumulada, así que la “ventana” no sabe que hay fronteras entre piezas. Cuando entiendes que estás manipulando una máscara y no un lápiz, esa limitación deja de ser un misterio y la solución —un elemento por pieza— se vuelve evidente.

⚔️ Revelar en lugar de dibujar
  1. Monta una firma con gsap.from y drawSVG: 0, y comprueba que sigue funcionando si cambias el path.
  2. Haz que un destello corto recorra un contorno cerrado en bucle con fromTo.
  3. Monta el trazo que crece desde el centro con "50% 50%" como origen.
  4. Coge un path con tres subcontornos y comprueba el salto entre piezas. Divídelo en tres elementos y escalona con stagger.
  5. Escala el SVG contenedor durante la animación y observa el desajuste. Añade live y compara.