Skip links

Hermoso SVG con propiedades personalizadas de CSS — Smashing Magazine

Hace poco expliqué cómo uso <symbol>, <use>y consultas de medios CSS para desarrollar lo que yo llamo SVG adaptativo. Los símbolos nos permiten definir un elemento a la vez. usar Una y otra vez, haciendo que las animaciones SVG sean más fáciles de mantener, más eficientes y más livianas.

Desde que escribí esta explicación, he diseñado e implementado nuevos Precioso 7 gráficos animados a lo largo mi sitio web. Utilizan un tema pionero de diseño web que presenta siete magníficos personajes del Viejo Oeste.

Gráficos con siete magníficos personajes del Viejo Oeste
Ver este SVG animado mi sitio web. (Vista previa grande)

<symbol> y <use> Permítanme definir un diseño de personaje y reutilizarlo en múltiples SVG y páginas. Primero creé mis personajes y puse a cada uno en un <symbol> En la biblioteca oculta SVG:

<!-- Symbols library -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
 <symbol id="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1">(...)</symbol>
 <symbol id="outlaw-2">(...)</symbol>
 <symbol id="outlaw-3">(...)</symbol>
 <!-- etc. -->
</svg>

Luego hice referencia a estos símbolos en otros dos SVG, uno para la pantalla grande y otro para la pantalla pequeña:

<!-- Large screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-large">
 <use href="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1" />
 <!-- ... -->
</svg>

<!-- Small screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-small">
 <use href="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1" />
 <!-- ... -->
</svg>

Elegante. Pero entonces sucedió algo exasperante. Puedo reutilizar los personajes, pero no puedo animarlos ni diseñarlos. Agregué una regla CSS para elementos dentro del símbolo al que hace referencia un <use>pero no pasó nada. Los colores permanecen iguales y las cosas que deberían estar en movimiento permanecen quietas. Sentí como si hubiera topado con un obstáculo invisible, y lo hice.

Comprensión de las barreras DOM en la sombra

Cuando cita el contenido de un symbol y useel navegador crea una copia del mismo en DOM en la sombra. cada <use> La instancia se convierte en una copia encapsulada de su propia referencia. <symbol>lo que significa que CSS desde fuera no puede romper la barrera para aplicar estilo directamente a cualquier elemento. Por ejemplo, en circunstancias normales, esto tapping El valor activa la animación CSS:

<g class="outlaw-1-foot tapping">
 <!-- ... -->
</g>
.tapping {
  animation: tapping 1s ease-in-out infinite;
}

Pero cuando se aplica la misma animación a <use> Tomando como ejemplo el mismo pie, no pasa nada:

<symbol id="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1">
 <g class="outlaw-1-foot"><!-- ... --></g>
</symbol>

<use href="#outlaw-1" class="tapping" />
.tapping {
  animation: tapping 1s ease-in-out infinite;
}

eso es porque <g> adentro <symbol> El elemento está en un árbol de sombra protegido y CSS Cascade está en <use> límite. Este comportamiento puede resultar frustrante, pero es intencionado, ya que garantiza que el contenido de los símbolos reutilizados siga siendo coherente y predecible.

Mientras aprendía a desarrollar SVG adaptativo, encontré varios intentos de resolver este problema, pero la mayoría sacrificó la reutilización que hace que SVG sea tan elegante. No quiero duplicar mis personajes sólo para que parpadeen en momentos diferentes. quiero uno <symbol> Instancias con tiempo y expresión propios.

Múltiples elementos animados en un solo símbolo SVG
Múltiples elementos animados contenidos en un solo símbolo SVG. (Vista previa grande)

Propiedades personalizadas de CSS al rescate

Mientras trabajaba en mi animación Pioneer, aprendí Los valores CSS regulares no pueden cruzar los límites del Shadow DOM, pero las propiedades personalizadas de CSS sí pueden. Aunque no puedes establecer estilos directamente dentro de los elementos <symbol>puede pasarles valores de propiedad personalizados. Entonces, cuando inserta atributos personalizados en un estilo en línea, el navegador ve la cascada y esos estilos están disponibles para los elementos en el estilo en línea. <symbol> Ser citado.

yo agregué rotate Se aplica a estilos en línea <symbol> contenido:

<symbol id="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1">
  <g class="outlaw-1-foot" style="
    transform-origin: bottom right; 
    transform-box: fill-box; 
    transform: rotate(var(--foot-rotate));">
    <!-- ... -->
  </g>
</symbol>

Luego, define la animación del paso y aplícala al elemento:

@keyframes tapping {
  0%, 60%, 100% { --foot-rotate: 0deg; }
  20% { --foot-rotate: -5deg; }
  40% { --foot-rotate: 2deg; }
}

use(data-outlaw="1") {
  --foot-rotate: 0deg;
  animation: tapping 1s ease-in-out infinite;
}

Pasar múltiples valores al símbolo.

Una vez que configuro un símbolo para usar propiedades personalizadas de CSS, puedo pasar cualquier cantidad de valores a cualquier <use> Ejemplo. Por ejemplo, puedo definir la variable fill, opacityo transform. Elegante es cada <symbol> Entonces la instancia puede tener su propio conjunto de valores.

<g class="eyelids" style="
  fill: var(--eyelids-colour, #f7bea1);
  opacity: var(--eyelids-opacity, 1);
  transform: var(--eyelids-scale, 0);"
>
  <!-- etc. -->
</g>
use(data-outlaw="1") {
  --eyelids-colour: #f7bea1; 
  --eyelids-opacity: 1;
}

use(data-outlaw="2") {
  --eyelids-colour: #ba7e5e; 
  --eyelids-opacity: 0;
}

La compatibilidad para pasar propiedades personalizadas de CSS como esta es sólida y todos los navegadores modernos manejan este comportamiento correctamente. Déjame mostrarte algunas formas en que uso esta técnica, comenzando con un sistema de íconos multicolores.

Sistema de iconos multicolores

Cuando necesito mantener un conjunto de íconos, puedo hacerlo en <symbol> Luego use propiedades personalizadas para aplicar colores y efectos. No es necesario copiar SVG para cada tema, cada use Puede llevar sus propios valores.

Propiedades personalizadas para el color de relleno en múltiples íconos de Bluesky
Propiedad personalizada para el color de relleno en varios iconos de Bluesky. (Vista previa grande)

Por ejemplo, apliqué --icon-fill Propiedades personalizadas predeterminadas fill color <path> En este icono de Bluesky:

<symbol id="icon-bluesky">
  <path fill="var(--icon-fill, currentColor)" d="..." />
</symbol>

Luego, cada vez que necesito cambiar la apariencia de ese ícono (por ejemplo, en <header> y <footer> — Puedo pasar nuevos fill Valores de color para cada instancia:

<header>
  <svg xmlns="http://www.w3.org/2000/svg">
    <use href="#icon-bluesky" style="--icon-fill: #2d373b;" />
  </svg>
</header>

<footer>
  <svg xmlns="http://www.w3.org/2000/svg">
    <use href="#icon-bluesky" style="--icon-fill: #590d1a;" />
  </svg>
</footer>

Estos íconos tienen la misma forma pero se ven diferentes debido a su estilo en línea.

Visualización de datos utilizando propiedades personalizadas de CSS.

podemos usar <symbol> y <use> De muchas formas más prácticas. También ayudan a crear visualizaciones de datos livianas, así que imagina una infografía sobre tres personajes famosos. salvaje oeste Alguacil: Wyatt Earp, Pat Garretty Bart Masterson.

Visualización de datos utilizando propiedades personalizadas de CSS.
Utilice propiedades personalizadas de CSS para la visualización de datos. (Vista previa grande)

El perfil de cada Sheriff utiliza el mismo conjunto SVG de tres símbolos: una barra que representa la duración de la carrera del Sheriff, otra que representa el número de arrestos y una que representa el número de muertes. Pasar valores de atributos personalizados a cada uno <use> Las instancias pueden cambiar la longitud de la barra, detener la escala y eliminar el color sin duplicar el SVG. Primero creé símbolos para estos proyectos:

<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
  <symbol id="career-bar">
    <rect
      height="10"
      width="var(--career-length, 100)" 
      fill="var(--career-colour, #f7bea1)"
    />
  </symbol>
  
  <symbol id="arrests-badge">
    <path 
      fill="var(--arrest-color, #d0985f)" 
      transform="scale(var(--arrest-scale, 1))"
    />
  </symbol>
  
  <symbol id="kills-icon">
    <path fill="var(--kill-colour, #769099)" />
  </symbol>
</svg>

Cada símbolo acepta uno o más valores:

  • --career-length Ajuste width Columna de ocupación.
  • --career-colour cambió fill El color de la barra.
  • --arrest-scale Controla el tamaño de las placas de arresto.
  • --kill-colour definido fill El color del icono de matar.

Puedo usarlos para desarrollar un perfil para cada Sheriff. <use> Elementos con diferentes estilos en línea, empezando por Wyatt Earp.

<svg xmlns="http://www.w3.org/2000/svg">
  <g id="wyatt-earp">
    <use href="#career-bar" style="--career-length: 400; --career-color: #769099;"/>
    <use href="#arrests-badge" style="--arrest-scale: 2;" />
    <!-- ... -->
    <use href="#arrests-badge" style="--arrest-scale: 2;" />
    <use href="#arrests-badge" style="--arrest-scale: 1;" />
    <use href="#kills-icon" style="--kill-color: #769099;" />
  </g>

  <g id="pat-garrett">
    <use href="#career-bar" style="--career-length: 300; --career-color: #f7bea1;"/>
    <use href="#arrests-badge" style="--arrest-scale: 2;" />
    <!-- ... -->
    <use href="#arrests-badge" style="--arrest-scale: 2;" />
    <use href="#arrests-badge" style="--arrest-scale: 1;" />
    <use href="#kills-icon" style="--kill-color: #f7bea1;" />
  </g>

  <g id="bat-masterson">
    <use href="#career-bar" style="--career-length: 200; --career-color: #c2d1d6;"/>
    <use href="#arrests-badge" style="--arrest-scale: 2;" />
    <!-- ... -->
    <use href="#arrests-badge" style="--arrest-scale: 2;" />
    <use href="#arrests-badge" style="--arrest-scale: 1;" />
    <use href="#kills-icon" style="--kill-color: #c2d1d6;" />
  </g>
</svg>

cada <use> Comparten los mismos elementos de símbolo, pero las variables en línea cambian de color y tamaño. Incluso puedo animar los valores para resaltar sus diferencias:

@keyframes pulse {
  0%, 100% { --arrest-scale: 1; }
  50% { --arrest-scale: 1.2; }
}

use(href="#arrests-badge"):hover {
  animation: pulse 1s ease-in-out infinite;
}

Las propiedades personalizadas de CSS no sólo ayudan con el estilo; También pueden transferir datos entre la geometría interna de HTML y SVG, vinculando propiedades visuales como color, longitud y escala con semánticas como arrestos, duración de la carrera y muertes.

animación ambiental

Comencé a aprender a animar elementos en símbolos mientras creaba gráficos animados para mi sitio web Magnificent 7. Para reducir la complejidad y hacer que mi código sea más liviano y fácil de mantener, necesito definir cada carácter una vez y reutilizarlo en el SVG:

<!-- Symbols library -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
  <symbol id="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1">(…)</symbol>
  <!-- ... -->
</svg>

<!-- Large screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-large">
  <use href="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1" />
  <!-- ... -->
</svg>

<!-- Small screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-small">
  <use href="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1" />
  <!-- ... -->
</svg>
Magníficos 7 personajes para mi sitio web
Magníficas 7 personas para mi sitio web. (Vista previa grande)

Pero no quería que estos personajes permanecieran estáticos; Necesitaba un movimiento sutil para darles vida. Quiero que sus ojos parpadeen, que sus pies golpeen, que sus barbas se muevan. Entonces, para animar estos detalles, utilicé propiedades personalizadas de CSS para pasar datos de animación a los elementos dentro de estos símbolos, comenzando con el parpadeo.

Logré el efecto de parpadeo colocando un grupo SVG sobre los ojos del forajido y luego cambiándolo opacity.

Crea un efecto de parpadeo animando la opacidad de los párpados.
Crea un efecto de parpadeo animando la opacidad de los párpados. (Vista previa grande)

Para lograr esto, agregué un estilo en línea con propiedades personalizadas de CSS al grupo:

<symbol id="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1" viewBox="0 0 712 2552">
 <g class="eyelids" style="opacity: var(--eyelids-opacity, 1);">
    <!-- ... -->
  </g>
</symbol>

Luego definí la animación de parpadeo cambiando --eyelids-opacity:

@keyframes blink {
  0%, 92% { --eyelids-opacity: 0; }
  93%, 94% { --eyelids-opacity: 1; }
  95%, 97% { --eyelids-opacity: 0.1; }
  98%, 100% { --eyelids-opacity: 0; }
}

…y aplicarlo a cada rol:

use(data-outlaw) {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  animation: blink var(--blink-duration) infinite var(--blink-delay);
}

…Para evitar que cada carácter parpadee al mismo tiempo, configuré diferentes --blink-delay Antes de que todos empiecen a parpadear, pasando otra propiedad personalizada:

use(data-outlaw="1") { --blink-delay: 1s; }
use(data-outlaw="2") { --blink-delay: 2s; }

use(data-outlaw="7") { --blink-delay: 3s; }
Efecto de golpeteo con animación de rotación del pie.
El efecto de golpeteo del pie se logra mediante la animación de rotación del pie. (Vista previa grande)

Algunos personajes golpean sus pies, así que también agregué estilos en línea con propiedades personalizadas de CSS a esos grupos:

<symbol id="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1" viewBox="0 0 712 2552">
  <g class="outlaw-1-foot" style="
    transform-origin: bottom right; 
    transform-box: fill-box; 
    transform: rotate(var(--foot-rotate));">
  </g>
</symbol>

Definir animación del pedal:

@keyframes tapping {
  0%, 60%, 100% { --foot-rotate: 0deg; }
  20% { --foot-rotate: -5deg; }
  40% { --foot-rotate: 2deg; }
}

Y agregue estas propiedades personalizadas adicionales a la declaración del rol:

use(data-outlaw) {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  --foot-rotate: 0deg;
  animation: 
    blink var(--blink-duration) infinite var(--blink-delay),
    tapping 1s ease-in-out infinite;
}
Logre efectos de tramado a través de la animación de traducción de barba
Logre efectos de tramado mediante la animación de la traducción de los bigotes. (Vista previa grande)

…y finalmente hacer que la barba del personaje tiemble mediante un estilo en línea con una propiedad personalizada de CSS que describe cómo se deforma su barba:

<symbol id="https://smashingmagazine.com/2025/11/smashing-animations-part-6-svgs-css-custom-properties/outlaw-1" viewBox="0 0 712 2552">
  <g class="outlaw-1-tashe" style="
    transform: translateX(var(--jiggle-x, 0px));"
  >
    <!-- ... -->
  </g>
</symbol>

Definir animación de fluctuación:

@keyframes jiggle {
  0%, 100% { --jiggle-x: 0px; }
  20% { --jiggle-x: -3px; }
  40% { --jiggle-x: 2px; }
  60% { --jiggle-x: -1px; }
  80% { --jiggle-x: 4px; }
}

Y agregue estas propiedades a la declaración del rol:

use(data-outlaw) {
  --blink-duration: 4s;
  --eyelids-opacity: 1;
  --foot-rotate: 0deg;
  --jiggle-x: 0px;
  animation: 
    blink var(--blink-duration) infinite var(--blink-delay),
    jiggle 1s ease-in-out infinite,
    tapping 1s ease-in-out infinite;
}

Con estas partes móviles, el personaje cobra vida, pero mi marcado sigue siendo muy simplificado. Al combinar varias animaciones en una sola declaración, puedo orquestar sus acciones sin agregar más elementos al SVG. Cada forajido tiene la misma base. <symbol>su personalidad proviene enteramente de las propiedades personalizadas de CSS.

Errores y soluciones

Aunque esta técnica parece infalible, existen algunos errores que es mejor evitar:

  • Las propiedades personalizadas de CSS solo se utilizan cuando var() uno adentro <symbol>. Olvídese de esto y se preguntará por qué no hay actualizaciones. Además, existen algunas propiedades heredadas de forma no natural, p. fill o transformnecesito usar var() El valor radica en beneficiarse de la cascada.
  • Es una buena idea incluir valores alternativos junto a las propiedades personalizadascomo opacity: var(--eyelids-opacity, 1); Garantiza que los elementos SVG se representen correctamente incluso sin aplicar valores de atributos personalizados.
  • Los estilos en línea se establecen por style Los atributos tienen prioridadpor lo que si combina CSS en línea y externo, recuerde que las propiedades personalizadas siguen reglas en cascada normales.
  • Siempre puede utilizar DevTools para inspeccionar valores de propiedades personalizados. elige uno <use> instancia y verifique el panel Estilos calculados para ver qué propiedades personalizadas están activas.

en conclusión

este <symbol> y <use> Los elementos son uno de los aspectos más elegantes pero a veces frustrantes de SVG. Las barreras de Shadow DOM hacen que sea más complicado animarlos, pero Las propiedades personalizadas de CSS actúan como un puente. Le permiten ofrecer color, movimiento y personalidad a través de límites invisibles, lo que da como resultado animaciones más claras, brillantes y, lo más importante, divertidas.

Gran editorial
(gg, yk)

Leave a comment

Home
Account
Cart
Search
¡Hola! ¡Pregúntame lo que quieras!
Explore
Drag