wandres.dev
ANIMAR SVG · CSS, SMIL y el DOM

Qué se puede animar con CSS en SVG y qué no

Atributos de presentación, propiedades de geometría y la lista de lo que queda fuera, con el estado de soporte de cada pieza.

⏱ 17 min

La frontera entre lo que CSS puede tocar dentro de un SVG y lo que no es históricamente confusa, porque se ha movido dos veces: primero con los atributos de presentación de SVG 1.1 y después con las propiedades de geometría de SVG 2. Saber exactamente dónde está la frontera hoy, y qué motor la respeta, ahorra la mitad de los intentos fallidos.

🎯 Al terminar esta lección sabrás
  • Distinguir atributo de presentación, propiedad de geometría y atributo puro.
  • Animar la geometría de una forma con CSS y saber en qué motores funciona.
  • Reconocer la lista de atributos que CSS no puede tocar.
  • Elegir la unidad correcta al animar dentro de un SVG.

Los atributos de presentación

Son la primera categoría y la más antigua. Un atributo de presentación es un atributo de SVG que es también una propiedad de CSS, con el mismo nombre y la misma sintaxis de valor. La lista útil para animación:

fill, fill-opacity, fill-rule, stroke, stroke-width, stroke-opacity, stroke-dasharray, stroke-dashoffset, stroke-linecap, stroke-linejoin, stroke-miterlimit, opacity, stop-color, stop-opacity, color, clip-path, mask, filter, marker-start, marker-mid, marker-end.

Todos ellos se animan con CSS sin ninguna consideración especial, con el mismo soporte que cualquier otra propiedad de CSS:

@keyframes latir {
  50% {
    fill: oklch(70% 0.2 20);
    stroke-width: 3;
  }
}

.corazon {
  fill: oklch(55% 0.18 20);
  stroke: oklch(30% 0.1 20);
  stroke-width: 2;
  animation: latir 1.2s ease-in-out infinite;
}

Una advertencia sobre la interpolación: no todos son interpolables. fill-rule, stroke-linecap y stroke-linejoin toman palabras clave y cambian de golpe en el punto medio, igual que visibility en HTML. stroke-dasharray sí interpola, pero solo si las dos listas tienen el mismo número de elementos; si no, salta.

Las propiedades de geometría

Es la aportación de SVG 2 y la que más posibilidades abre. Nueve atributos que antes solo eran atributos pasaron a ser propiedades de CSS de pleno derecho:

cx, cy, r, rx, ry, x, y, width, height.

Con eso se puede animar la forma de los primitivos sin tocar el DOM:

@keyframes expandir {
  from { r: 4; }
  to   { r: 28; }
}

.onda {
  r: 4;
  fill: none;
  stroke: currentColor;
  animation: expandir 1.6s cubic-bezier(0.2, 0, 0, 1) infinite;
}
<svg viewBox="0 0 64 64" width="64" height="64">
  <circle class="onda" cx="32" cy="32" />
</svg>

El soporte es sólido: Chromium desde la 43, Safari desde la 9 y Firefox desde la 69. Es de las pocas cosas de este nivel que se pueden dar por universales.

Dos detalles que evitan sorpresas. El primero: los valores van sin unidad o con unidades de CSS, y sin unidad significan unidades de usuario del sistema de coordenadas del SVG, que es lo que quieres casi siempre. El segundo: como ahora son propiedades de CSS, cualquier regla las pisa, incluido un r que hubiera en el marcado. Es el mismo comportamiento de especificidad cero de los atributos de presentación.

Lo que queda fuera

Esta es la lista que hay que conocer para no perder el tiempo, porque el fallo es siempre silencioso: la declaración se descarta y no pasa nada.

Atributo Dónde aparece Alternativa
points polygon, polyline SMIL, o convertir a path
x1, y1, x2, y2 line SMIL, o convertir a path
viewBox svg, symbol, marker SMIL o el DOM
offset stop de un degradado SMIL o el DOM
preserveAspectRatio svg, image El DOM
pathLength formas geométricas No suele hacer falta animarlo
patternTransform pattern SMIL

La conversión a path que aparece dos veces en la tabla es más útil de lo que parece: una línea y un polígono se pueden reescribir como trazados, y entonces su forma vive en el atributo d. Lo cual nos lleva al caso especial.

El caso de d, y por qué el morphing de formas con CSS sigue sin ser una opción

La propiedad de CSS d es la excepción que hay que conocer con precisión porque promete exactamente lo que todo el mundo quiere —interpolar entre dos formas de trazado con @keyframes— y no está en Safari. Y no está de la peor manera posible: la propiedad se parsea sin error y no tiene ningún efecto. No hay declaración inválida que se descarte, no hay nada que @supports pueda detectar de forma fiable, y el @keyframes se aplica sin producir cambio alguno. El resultado en Safari es una animación que corre, consume su tiempo y no mueve nada. Está en Chromium desde la 52 y en Firefox desde la 97, así que es una de esas capacidades que llevan años a un motor de completarse. Hay una segunda limitación que afecta también a los motores que sí la implementan y que conviene conocer antes de intentar nada: la interpolación de d exige que los dos trazados tengan el mismo número de comandos y del mismo tipo, en el mismo orden. Dos formas que no cumplan eso saltan en lugar de interpolar, y “el mismo número de comandos” es una condición que ningún diseñador va a respetar por su cuenta. Es decir, incluso donde funciona, el morphing con CSS obliga a preparar los trazados a mano para que sean compatibles. Esas dos cosas juntas explican por qué el morphing de formas sigue siendo territorio de librerías: lo que aporta un plugin como MorphSVG no es la interpolación, que es fácil, sino el emparejamiento automático de puntos entre trazados que no se parecen en nada, que es el problema difícil y que ninguna especificación resuelve.

Las unidades dentro de un SVG

Una fuente de confusión que merece cerrarse. Dentro de un SVG, un número sin unidad es una unidad de usuario, y una unidad de usuario equivale a un píxel de CSS en el sistema de coordenadas local, que el viewBox puede haber escalado.

La consecuencia práctica: si tu SVG tiene viewBox="0 0 100 100" y se muestra a 400 píxeles de ancho, cada unidad de usuario se pinta como cuatro píxeles. Un stroke-width: 2 se ve de ocho píxeles. Y una animación que vaya de r: 4 a r: 28 recorre, en pantalla, de dieciséis a ciento doce píxeles.

Eso es normalmente una ventaja: las animaciones escalan con el gráfico automáticamente y no hay que recalcular nada al cambiar el tamaño. Se convierte en un problema en un solo caso, y conviene reconocerlo: cuando quieres un grosor de trazo constante en pantalla independientemente del zoom. Para eso existe vector-effect: non-scaling-stroke, que desacopla el trazo del sistema de coordenadas y es propiedad de CSS, así que también se puede animar y consultar.

Los porcentajes tienen su propia regla y es de las más olvidadas: dentro de un SVG, un porcentaje en stroke-width, stroke-dasharray o stroke-dashoffset no se resuelve contra el ancho ni contra el alto, sino contra la diagonal normalizada del viewport actual, que es la raíz cuadrada de la suma de los cuadrados de ancho y alto, dividida por la raíz de dos. Es una fórmula que existe para que un porcentaje se comporte razonablemente en las dos dimensiones a la vez, y es la razón de que un stroke-dasharray: 50% nunca dé el resultado que uno espera a ojo. Con trazos, usa números.