Maquetación Web Dinámica / SVG: dibujar y animar vectores
Animar SVG con CSS: giros, gradientes e iconos que reaccionan
Un SVG inline es HTML con superpoderes: cada parte es un elemento que tu
CSS puede seleccionar, transformar y animar. Todo el arsenal de los
módulos 10 y 11 — transform, transition, @keyframes — funciona sobre
vectores. Pero hay UNA trampa que hace tropezar a todos la primera vez, y
hoy la resuelves de entrada.
La trampa: en SVG, el transform gira sobre el origen del lienzo
La vacuna son dos líneas que le dicen al elemento "tu chincheta va en tu propio centro":
transform-box: fill-box reencuadra el transform-origin para que
center signifique "el centro de esta forma". Con esas dos líneas, todo
lo que aprendiste de transformaciones funciona igual sobre SVG. Grábalas:
son el 80% de los "¿por qué mi icono SVG gira raro?".
Con y sin la vacuna, lado a lado
<div class="fila">
<figure>
<svg viewBox="0 0 100 100" width="120" height="120">
<rect class="mala" x="38" y="38" width="24" height="24" rx="5" fill="#ef4444"/>
</svg>
<figcaption>sin transform-box: orbita la esquina</figcaption>
</figure>
<figure>
<svg viewBox="0 0 100 100" width="120" height="120">
<rect class="buena" x="38" y="38" width="24" height="24" rx="5" fill="#6d28d9"/>
</svg>
<figcaption>con fill-box + center: gira en su sitio</figcaption>
</figure>
</div>
<p class="nota">Misma forma, misma animación rotate(360deg). La única diferencia son las dos líneas de la vacuna.</p>body { font-family: system-ui; padding: 20px; background: #f8fafc; }
.fila { display: flex; gap: 40px; flex-wrap: wrap; }
figure { margin: 0; text-align: center; }
svg { background: white; border: 1px solid #e2e8f0; border-radius: 12px; overflow: visible; }
.mala { animation: girar 3s linear infinite; }
.buena { animation: girar 3s linear infinite; transform-box: fill-box; transform-origin: center; }
@keyframes girar { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .mala, .buena { animation: none; } }
figcaption { font-size: 12px; color: #64748b; margin-top: 10px; max-width: 150px; }
.nota { font-size: 13px; color: #64748b; margin-top: 16px; max-width: 54ch; }Iconos que reaccionan al hover
Como cada parte es un elemento, puedes animar el interior de un icono en
respuesta al :hover del contenedor — el detalle que da vida a los
botones profesionales:
<div class="fila">
<button class="corazon-btn" aria-label="Me gusta">
<svg viewBox="0 0 24 24" width="34" height="34">
<path class="corazon" d="M12 21s-8-4.5-8-10a4.5 4.5 0 0 1 8-2.8A4.5 4.5 0 0 1 20 11c0 5.5-8 10-8 10z"
fill="none" stroke="#6d28d9" stroke-width="2"/>
</svg>
</button>
<button class="flecha-btn">
Siguiente
<svg viewBox="0 0 24 24" width="20" height="20"><path class="flecha" d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
<p class="nota">Hover en el corazón: se rellena y late. Hover en el botón: la flecha se desliza. Todo con transiciones CSS sobre partes del SVG.</p>body { font-family: system-ui; padding: 24px; background: #f8fafc; }
.fila { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.corazon-btn { background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 8px; cursor: pointer; }
.corazon { transition: fill 200ms ease-out, transform 200ms ease-out; transform-box: fill-box; transform-origin: center; }
.corazon-btn:hover .corazon, .corazon-btn:focus-visible .corazon { fill: #6d28d9; transform: scale(1.12); }
.flecha-btn { display: inline-flex; align-items: center; gap: 8px; background: #6d28d9; color: white; border: 0; border-radius: 10px; padding: 11px 18px; font-size: 14px; font-weight: 600; cursor: pointer; }
.flecha { transition: transform 200ms ease-out; }
.flecha-btn:hover .flecha, .flecha-btn:focus-visible .flecha { transform: translateX(4px); }
@media (prefers-reduced-motion: reduce) { .corazon, .flecha { transition: none; } }
.nota { font-size: 13px; color: #64748b; margin-top: 16px; max-width: 54ch; }Nota el stroke="currentColor" de la flecha (lección 4): hereda el
blanco del botón, y si mañana cambias el color del texto, la flecha lo
sigue sola.
Gradientes que fluyen: cuando CSS no alcanza
Un <linearGradient> como relleno es fácil; hacer que sus colores FLUYAN
es donde CSS se queda corto — no puede animar los <stop> internos. Ahí
entra SMIL, el sistema de animación NATIVO del SVG, que vive dentro
del propio dibujo:
<svg viewBox="0 0 200 60">
<defs>
<linearGradient id="flujo" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#6d28d9">
<animate attributeName="stop-color" values="#6d28d9;#a78bfa;#6d28d9" dur="3s" repeatCount="indefinite"/>
</stop>
<stop offset="1" stop-color="#a78bfa">
<animate attributeName="stop-color" values="#a78bfa;#6d28d9;#a78bfa" dur="3s" repeatCount="indefinite"/>
</stop>
</linearGradient>
</defs>
<rect width="200" height="60" rx="10" fill="url(#flujo)"/>
</svg>Nota honesta sobre SMIL: funciona HOY en todos los navegadores, pero el ecosistema apuesta por CSS y por librerías como GSAP (módulo 16) para lo complejo — SMIL quedó como "soportado pero sin futuro activo". La regla de oro: anima con CSS lo que CSS alcance (transforms, opacidad, fills, strokes, dashoffset), y reserva SMIL o GSAP para lo que no (stops de gradiente, morphing avanzado). No necesitas dominar SMIL; necesitas saber que existe y para qué.
Animas una manecilla de reloj (un rect alto) con rotate, y en vez de girar sobre su base, sale volando en un círculo enorme por la pantalla. ¿El arreglo?
Mini-reto
Toma un icono de engranaje o de estrella (Lucide tiene ambos), pégalo
inline y hazlo girar en loop — pero solo funcionará bien cuando le pongas
la vacuna de las dos líneas. Pruébalo PRIMERO sin ellas para ver la órbita
del error en tu propia pantalla; después arréglalo. Sentir el bug antes de
la cura es la mejor forma de no olvidar transform-box.
Qué sigue
Ya giras, rellenas y reaccionas. Quedan los dos efectos que parecen de otra liga: el MORPHING (una forma que se transforma en otra, como un icono de play que se vuelve pausa) y los FILTROS SVG, con los que se hace el famoso efecto "gooey". El gran final técnico del módulo, antes del reto.