J’ai expliqué récemment comment j’utilise <symbol>, <use>et CSS Media Queries pour développer ce que j’appelle SVG adaptatifs. Les symboles nous permettent de définir un élément une fois, puis utiliser encore et encore, ce qui rend les animations SVG plus faciles à maintenir, plus efficaces et plus légères.
Depuis que j’ai écrit cette explication, j’ai conçu et implémenté de nouveaux Magnifique 7 graphiques animés à travers mon site internet. Ils jouent sur le thème des pionniers du design Web, mettant en vedette sept magnifiques personnages du Far West.
<symbol> et <use> permettez-moi de définir une conception de personnage et de la réutiliser sur plusieurs SVG et pages. Tout d’abord, j’ai créé mes personnages et mis chacun dans un <symbol> à l’intérieur d’une bibliothèque cachée 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>
Ensuite, j’ai référencé ces symboles dans deux autres SVG, l’un pour les grands écrans et l’autre pour les petits écrans :
<!-- 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>
Élégant. Mais ensuite est venu l’exaspérant. Je pouvais réutiliser les personnages, mais je ne pouvais pas les animer ni les styliser. J’ai ajouté des règles CSS ciblant les éléments au sein des symboles référencés par un <use>mais rien ne s’est passé. Les couleurs sont restées les mêmes et les objets qui devraient bouger sont restés statiques. J’avais l’impression de tomber sur une barrière invisible, et c’était le cas.
Comprendre la barrière Shadow DOM
Lorsque vous faites référence au contenu d’un symbol avec useun navigateur en crée une copie dans le Ombre DOM. Chaque <use> l’instance devient sa propre copie encapsulée de l’instance référencée <symbol>ce qui signifie que les CSS de l’extérieur ne peuvent pas franchir la barrière pour styliser directement les éléments. Par exemple, dans des circonstances normales, cela tapping value déclenche une animation CSS :
<g class="outlaw-1-foot tapping">
<!-- ... -->
</g>
.tapping {
animation: tapping 1s ease-in-out infinite;
}
Mais lorsque la même animation est appliquée à un <use> exemple de ce même pied, rien ne se passe :
<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;
}
C’est parce que le <g> à l’intérieur du <symbol> L’élément est dans une arborescence fantôme protégée et la cascade CSS s’arrête net au <use> limite. Ce comportement peut être frustrant, mais il est intentionnel car il garantit que le contenu des symboles réutilisés reste cohérent et prévisible.
En apprenant à développer des SVG adaptatifs, j’ai découvert toutes sortes de tentatives pour contourner ce comportement, mais la plupart d’entre elles sacrifiaient la réutilisabilité qui rend le SVG si élégant. Je ne voulais pas dupliquer mes personnages juste pour les faire clignoter à des moments différents. j’en voulais un seul <symbol> avec des instances qui ont leurs propres timings et expressions.
Propriétés personnalisées CSS à la rescousse
En travaillant sur mes animations pionnières, j’ai appris que les valeurs CSS normales ne peuvent pas franchir la limite du Shadow DOM, mais les propriétés CSS personnalisées le peuvent.. Et même si vous ne pouvez pas styliser directement les éléments à l’intérieur d’un <symbol>vous pouvez leur transmettre des valeurs de propriétés personnalisées. Ainsi, lorsque vous insérez des propriétés personnalisées dans un style en ligne, un navigateur examine la cascade et ces styles deviennent disponibles pour les éléments à l’intérieur du style en ligne. <symbol> étant référencé.
j’ai ajouté rotate à un style en ligne appliqué au <symbol> contenu:
<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>
Ensuite, définissez l’animation de tapotement du pied et appliquez-la à l’élément :
@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;
}
Passer plusieurs valeurs à un symbole
Une fois que j’ai configuré un symbole pour utiliser les propriétés personnalisées CSS, je peux transmettre autant de valeurs que je le souhaite. <use> exemple. Par exemple, je pourrais définir des variables pour fill, opacityou transform. Ce qui est élégant c’est que chacun <symbol> l’instance peut alors avoir son propre ensemble de valeurs.
<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 prise en charge de la transmission de propriétés personnalisées CSS comme celle-ci est solide et tous les navigateurs contemporains gèrent ce comportement correctement. Laissez-moi vous montrer quelques façons dont j’utilise cette technique, en commençant par un système d’icônes multicolores.
Un système d’icônes multicolores
Lorsque j’ai besoin de conserver un ensemble d’icônes, je peux définir une icône une fois dans un <symbol> puis utilisez des propriétés personnalisées pour appliquer des couleurs et des effets. Au lieu de devoir dupliquer les SVG pour chaque thème, chaque use peut porter ses propres valeurs.
Par exemple, j’ai appliqué un --icon-fill propriété personnalisée pour la valeur par défaut fill couleur du <path> dans cette icône Bluesky :
<symbol id="icon-bluesky">
<path fill="var(--icon-fill, currentColor)" d="..." />
</symbol>
Ensuite, chaque fois que j’ai besoin de modifier l’apparence de cette icône, par exemple dans un <header> et <footer> — Je peux passer du nouveau fill valeurs de couleur pour chaque instance :
<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>
Ces icônes ont la même forme mais sont différentes grâce à leurs styles en ligne.
Visualisations de données avec propriétés personnalisées CSS
Nous pouvons utiliser <symbol> et <use> de manière bien plus pratique. Ils sont également utiles pour créer des visualisations de données légères, alors imaginez une infographie sur trois célèbres Le Far West shérifs : Wyatt Earp, Pat Garrettet Chauve-souris Masterson.
Le profil de chaque shérif utilise le même ensemble de trois symboles SVG : un pour une barre représentant la durée de la carrière d’un shérif, un autre pour représenter le nombre d’arrestations effectuées et un autre pour le nombre de victimes. Passer des valeurs de propriétés personnalisées à chacun <use> L’exemple peut faire varier la longueur des barres, l’échelle des arrestations et tuer la couleur sans dupliquer les SVG. J’ai d’abord créé des symboles pour ces éléments :
<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>
Chaque symbole accepte une ou plusieurs valeurs :
--career-lengthajuste lewidthdu barreau de carrière.--career-colourchange lefillcouleur de cette barre.--arrest-scalecontrôle la taille du badge d’arrestation.--kill-colourdéfinit lefillcouleur de l’icône de mise à mort.
Je peux les utiliser pour développer un profil de chaque shérif en utilisant <use> éléments avec différents styles en ligne, à commencer par 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>
Chaque <use> partage les mêmes éléments de symbole, mais les variables en ligne changent de couleur et de taille. Je peux même animer ces valeurs pour mettre en valeur leurs différences :
@keyframes pulse {
0%, 100% { --arrest-scale: 1; }
50% { --arrest-scale: 1.2; }
}
use[href="#arrests-badge"]:hover {
animation: pulse 1s ease-in-out infinite;
}
Animations ambiantes
J’ai commencé à apprendre à animer des éléments dans des symboles tout en créant les graphiques animés pour Magnificent 7 de mon site Web. Pour réduire la complexité et rendre mon code plus léger et plus maintenable, je devais définir chaque caractère une fois et le réutiliser dans les 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>
Mais je ne voulais pas que ces personnages restent statiques ; J’avais besoin de mouvements subtils qui leur donneraient vie. Je voulais que leurs yeux clignent, que leurs pieds tapent et que leurs moustaches se contractent. Ainsi, pour animer ces détails, je transmets les données d’animation aux éléments à l’intérieur de ces symboles à l’aide des propriétés personnalisées CSS, en commençant par le clignotement.
J’ai implémenté l’effet clignotant en plaçant un groupe SVG sur les yeux des hors-la-loi, puis en modifiant son opacity.
Pour rendre cela possible, j’ai ajouté un style en ligne avec une propriété personnalisée CSS au groupe :
<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>
Ensuite, j’ai défini l’animation clignotante en changeant --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; }
}
…et je l’ai appliqué à chaque personnage :
use[data-outlaw] {
--blink-duration: 4s;
--eyelids-opacity: 1;
animation: blink var(--blink-duration) infinite var(--blink-delay);
}
…pour que chaque personnage ne cligne pas des yeux en même temps, j’ai défini un --blink-delay avant qu’ils ne commencent tous à clignoter, en passant une autre propriété personnalisée :
use[data-outlaw="1"] { --blink-delay: 1s; }
use[data-outlaw="2"] { --blink-delay: 2s; }
use[data-outlaw="7"] { --blink-delay: 3s; }
Certains personnages tapent du pied, j’ai donc également ajouté un style en ligne avec une propriété personnalisée CSS à ces groupes :
<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>
Définition de l’animation de frappe du pied :
@keyframes tapping {
0%, 60%, 100% { --foot-rotate: 0deg; }
20% { --foot-rotate: -5deg; }
40% { --foot-rotate: 2deg; }
}
Et en ajoutant ces propriétés personnalisées supplémentaires à la déclaration des caractères :
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;
}
…avant de finalement faire bouger les moustaches du personnage via un style en ligne avec une propriété CSS personnalisée qui décrit comment sa moustache se transforme :
<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>
Définir l’animation de jiggle :
@keyframes jiggle {
0%, 100% { --jiggle-x: 0px; }
20% { --jiggle-x: -3px; }
40% { --jiggle-x: 2px; }
60% { --jiggle-x: -1px; }
80% { --jiggle-x: 4px; }
}
Et en ajoutant ces propriétés à la déclaration des caractères :
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;
}
Avec ces pièces mobiles, les personnages prennent vie, mais mon balisage reste remarquablement simple. En combinant plusieurs animations en une seule déclaration, je peux chorégraphier leurs mouvements sans ajouter plus d’éléments à mon SVG. Tous les hors-la-loi partagent la même base <symbol>et leur individualité provient entièrement des propriétés personnalisées CSS.
Pièges et solutions
Même si cette technique peut sembler infaillible, il y a quelques pièges qu’il vaut mieux éviter :
- Les propriétés personnalisées CSS ne fonctionnent que si elles sont référencées par un
var()à l’intérieur d’un<symbol>. Oubliez cela et vous vous demanderez pourquoi rien n’est mis à jour. De plus, les propriétés qui ne sont pas naturellement héritées, commefilloutransformil faut utiliservar()dans leur valeur pour bénéficier de la cascade. - Il est toujours préférable d’inclure une valeur de secours à côté d’une propriété personnaliséecomme
opacity: var(--eyelids-opacity, 1);pour garantir que les éléments SVG s’affichent correctement même sans l’application de valeurs de propriétés personnalisées. - Styles en ligne définis via le
stylel’attribut est prioritairedonc si vous mélangez CSS en ligne et externe, n’oubliez pas que les propriétés personnalisées suivent les règles de cascade normales. - Vous pouvez toujours utiliser DevTools pour inspecter les valeurs des propriétés personnalisées. Sélectionnez un
<use>instance et vérifiez le panneau Styles calculés pour voir quelles propriétés personnalisées sont actives.
Conclusion
Le <symbol> et <use> Les éléments sont parmi les aspects les plus élégants mais parfois frustrants du SVG. La barrière Shadow DOM rend leur animation plus délicate, mais Les propriétés personnalisées CSS agissent comme un pont. Ils vous permettent de transmettre la couleur, le mouvement et la personnalité au-delà de cette frontière invisible, ce qui donne lieu à des animations plus propres, plus légères et, surtout, amusantes.
(gg, ouais)
Source link

