wandres.dev
GSAP X · Plugins de SVG

MorphSVG y el problema de la correspondencia entre formas

Por qué morfear entre dos paths con distinto número de puntos es difícil, qué hace el plugin por debajo, y las tres decisiones que tiene que tomar por ti.

⏱ 19 min

Morfear entre dos formas parece trivial hasta que lo intentas: interpolas los puntos del origen con los del destino y ya está. El problema es que un diamante tiene cuatro puntos y un rayo tiene siete, que uno usa segmentos rectos y el otro curvas cúbicas y arcos, y que aunque tuvieran el mismo número de puntos no habría ninguna garantía de que el primero de uno corresponda al primero del otro. MorphSVG resuelve las tres cosas, y saber cuál de las tres está fallando cuando un morfeo se ve mal es lo que te permite arreglarlo en lugar de probar valores al azar.

🎯 Al terminar esta lección sabrás
  • Explicar los tres problemas de morfear entre dos paths arbitrarios.
  • Describir qué transformación aplica el plugin a los datos de ambas formas.
  • Convertir formas primitivas de SVG a paths para poder morfearlas.
  • Reconocer cuándo un morfeo malo es de correspondencia, de orden o de tipo de interpolación.

Tres problemas, no uno

El atributo d de un path es una cadena de comandos: M para mover, L para líneas, C para curvas cúbicas, Q para cuadráticas, A para arcos, Z para cerrar, cada uno con su versión relativa en minúscula. Interpolar dos de esas cadenas letra a letra no significa nada.

El primer problema es de tipos de comando. Un L y un C no se pueden mezclar porque tienen distinto número de parámetros y distinta semántica.

El segundo es de cantidad. Si el origen tiene cuatro anclas y el destino siete, faltan tres puntos en algún lado, y hay que fabricarlos sin que la forma inicial cambie de aspecto.

El tercero, el más sutil y el que produce los morfeos feos, es de correspondencia. Un contorno cerrado se dibuja empezando por algún punto y siguiendo un sentido de giro. Si el origen empieza en la esquina superior izquierda y el destino en la inferior derecha, emparejar el primer punto con el primero, el segundo con el segundo y así sucesivamente hace que la forma se retuerza sobre sí misma y se invierta a mitad del recorrido.

MorphSVG ataca los tres. Convierte todos los comandos a curvas cúbicas de Bézier, que pueden representar cualquier segmento incluidas las rectas. Subdivide segmentos para igualar la cantidad de anclas sin alterar la apariencia. Y busca una correspondencia razonable entre los puntos de ambas formas.

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

gsap.registerPlugin(MorphSVGPlugin);

gsap.to("#diamante", { duration: 1, morphSVG: "#rayo" });

Esa línea funciona: encuentra los dos paths, hace todo lo anterior por debajo, y anima el atributo d.

Las cuatro formas del valor

morphSVG acepta cuatro cosas distintas.

// 1) Texto de selector: coge la "d" del elemento correspondiente
gsap.to("#diamante", { morphSVG: "#rayo" });

// 2) Una referencia directa al elemento
const destino = document.getElementById("rayo");
gsap.to("#diamante", { morphSVG: destino });

// 3) Datos de path crudos
gsap.to("#diamante", { morphSVG: "M47.1,0.8 73.3,0.8 61.9,37.2 77.1,37.2 30.7,99.4 45.8,51.9 29,51.9z" });

// 4) Puntos, para polygon y polyline
gsap.to("#poligono", { morphSVG: "240,220 240,70 70,70 70,220" });

Y hay un detalle que ahorra código: el plugin guarda los datos originales en el elemento, en un atributo data-original, de modo que morfear de vuelta al inicio es tan sencillo como apuntar al propio elemento.

const tl = gsap.timeline({ repeat: -1, repeatDelay: 0.8 });
tl.to("#forma", { duration: 0.8, morphSVG: "#forma-b" })
  .to("#forma", { duration: 0.8, morphSVG: "#forma-c" }, "+=0.8")
  .to("#forma", { duration: 0.8, morphSVG: "#forma" }, "+=0.8"); // vuelta al original

Solo se morfean paths

Técnicamente solo se pueden morfear elementos path, y también polyline y polygon. Un circle, un rect, un ellipse o un line no tienen atributo d.

Si le pasas uno de esos como destino, el plugin genera internamente los datos de path equivalentes y funciona. Si el que quieres morfear es uno de esos, hay que convertirlo primero.

// Sustituye el elemento por un <path> equivalente, conservando id y atributos
MorphSVGPlugin.convertToPath("#cuadrado");

// O de golpe, todos los primitivos del documento
MorphSVGPlugin.convertToPath("circle, rect, ellipse, line, polygon, polyline");

La conversión intercambia el nodo en el DOM por uno nuevo con la misma apariencia y los mismos atributos, incluido el id, así que después puedes seguir seleccionándolo igual.

<!-- Antes -->
<rect id="cuadrado" width="100" height="100" fill="red"/>

<!-- Despues de convertToPath -->
<path id="cuadrado" fill="red" d="M100,0 v100 h-100 v-100 h100z"></path>
ℹ️
Los paths de varios segmentos se manejan por partes

Un path cuyo atributo d contiene varios comandos M tiene varios subcontornos: un icono con un agujero, un logotipo con tres piezas. El plugin trata cada subcontorno por separado y tiene que decidir cuál del origen se empareja con cuál del destino. Esa decisión se controla con map, que verás en la lección siguiente. Cuando el emparejamiento salga mal y ninguna opción lo arregle, la recomendación oficial es dividir el path en varios elementos y morfear cada uno por su cuenta, que da control total.

Cuándo un morfeo se ve mal, y por qué

Tres síntomas, tres causas distintas, tres remedios distintos. Reconocerlos ahorra horas.

La forma se retuerce y se invierte a mitad del recorrido. Es un problema de correspondencia: los puntos de origen y destino se han emparejado con un desfase que hace que el contorno se cruce consigo mismo. El remedio es shapeIndex, que desplaza el emparejamiento.

La forma llega bien pero por el camino aparecen picos o pliegues donde el original era suave. Es un problema del tipo de interpolación: al mover las anclas y sus manejadores en línea recta, un ancla que era suave deja de serlo temporalmente. Los remedios son type: "rotational" o curveMode: true.

Los segmentos del origen se emparejan con los segmentos equivocados del destino. Es un problema de mapeo de subcontornos, y se ajusta con map o se elimina separando el path.

Los tres, con sus opciones exactas, están en la lección siguiente.

Y hay un cuarto síntoma que no es de morfeo sino de rendimiento: un tirón perceptible solo en el primer morfeo. Ese es el coste de todos los cálculos anteriores, que se hacen la primera vez que el tween se renderiza. Para formas muy complejas existe precompile, que permite calcular esos valores una vez, copiarlos de la consola y pegarlos en el tween para que el primer render sea instantáneo.

// 1) Ejecuta esto una vez y mira la consola
gsap.to("#cerebro", { duration: 1, morphSVG: { shape: "#letras", precompile: "log" } });

// 2) Copia el array que ha salido y pegalo aqui
gsap.to("#cerebro", {
  duration: 1,
  morphSVG: {
    shape: "#letras",
    precompile: ["M0,0 C100,200 120,500 300,145 34,245 560,46", "M0,0 C200,300 100,400 230,400 100,456 400,300"],
  },
});

precompile solo funciona sobre elementos path, no sobre polyline ni polygon, y hay que rehacerlo si cambias las formas. Y conviene ser honesto sobre su alcance: solo mejora el primer render. Si el morfeo va a tirones durante toda la animación, el cuello de botella es el motor de renderizado de SVG del navegador con una forma demasiado compleja, y eso no lo arregla ningún plugin.

Morfear es un problema de emparejamiento, y por eso ninguna heurística acierta siempre

Conviene entender por qué este plugin tiene tantas opciones de ajuste cuando otros no tienen ninguna, porque la respuesta no es que esté mal diseñado sino que el problema subyacente está mal definido. Cuando dices “morfea este diamante en este rayo”, estás describiendo el resultado inicial y el final, pero no el camino, y el camino no está determinado por ellos: hay infinitas maneras de deformar continuamente un contorno en otro, y todas son igual de correctas desde el punto de vista matemático. Lo que hace que unas se vean bien y otras horribles es una noción de “naturalidad” que vive en la percepción humana y no en los datos. Formalizarla es lo que en informática gráfica se conoce como el problema de la correspondencia, y aparece exactamente igual en interpolación de mallas 3D, en el morfeo de imágenes y en la animación de personajes; en todos esos campos sigue siendo un problema abierto que se aborda con heurísticas y con intervención humana, no con una solución cerrada. MorphSVG codifica un conjunto de heurísticas muy buenas —emparejar por tamaño, luego por posición, subdividir para igualar cantidades, elegir un desplazamiento de índice que minimice el cruce— y acierta la inmensa mayoría de las veces. Pero no puede acertar siempre, porque acertar significaría conocer tu intención, y tu intención no está en los datos: dos personas distintas pueden querer dos caminos distintos entre las mismas dos formas. Por eso las opciones de ajuste no son parches sobre un algoritmo defectuoso; son el canal por el que aportas la información que falta. Y por eso la recomendación de la documentación cuando nada funciona —divide el path y morfea cada pieza aparte— no es una rendición: es reconocer que a veces lo que hay que aportar no es un parámetro sino una descomposición del problema, que es información estructural que solo tienes tú.

⚔️ Ver el emparejamiento
  1. Morfea un cuadrado en una estrella de cinco puntas y observa si se retuerce.
  2. Convierte un rect y un circle a paths con convertToPath e inspecciona el d resultante de cada uno.
  3. Monta una timeline que recorra cuatro formas y vuelva a la original usando el propio selector como destino final.
  4. Coge un icono con dos subcontornos y morfealo a otro con dos, y comprueba si los empareja como esperabas.
  5. Mide el coste del primer morfeo de una forma compleja en el panel de rendimiento y aplícale precompile.