Thèmes

Sprites SVG

Un sprite est un fichier SVG unique qui contient un symbole par icône. Les pages référencent les symboles avec <use>, le navigateur télécharge et met en cache le fichier une seule fois, et aucun JavaScript ne s'exécute.

Obtenir le sprite

Le paquet fournit, sous glyphfox/sprite.svg, un sprite contenant toutes les icônes. Chaque icône est un <symbol> dont l'id est gf- suivi du nom de l'icône, avec son propre viewBox et les réglages de trait du jeu d'icônes.

terminal
npm install glyphfox
cp node_modules/glyphfox/dist/sprite.svg public/icons/sprite.svg
sprite.svg
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="gf-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">...</symbol>
<symbol id="gf-search" viewBox="0 0 24 24" ...>...</symbol>
...
</svg>

Avec un bundler, vous pouvez plutôt lui confier la copie du fichier et l'ajout d'une empreinte à son nom. Dans Vite, le suffixe ?url renvoie l'URL finale de la ressource :

icons.js
import spriteUrl from 'glyphfox/sprite.svg?url';

export const icon = (name) =>
  `<svg class="icon" width="24" height="24" aria-hidden="true"><use href="${spriteUrl}#gf-${name}"></use></svg>`;

Le sprite complet contient les 419 icônes en 117,9 Ko, soit 15,7 Ko avec gzip. Un sprite avec les 30 icônes d'un app shell typique pèse 7,5 Ko, 1,3 Ko avec gzip ; la CLI en génère un pour vous, comme indiqué ci-dessous.

Référencer un symbole

Écrivez un svg englobant de la taille voulue, avec un élément use qui pointe vers le symbole. Le symbole apporte son propre viewBox : l'élément englobant n'a donc besoin que d'une largeur et d'une hauteur.

index.html
<button type="button" class="btn">
  <svg class="icon" width="20" height="20" aria-hidden="true">
    <use href="/icons/sprite.svg#gf-download"></use>
  </svg>
  Download
</button>
app.css
.icon {
  width: 1.25em;
  height: 1.25em;
  flex-shrink: 0;
  vertical-align: -0.2em;
}
.btn:hover .icon {
  color: #ff7a1a;
}

Les symboles sont tracés avec stroke="currentColor", et la propriété color est héritée jusque dans le contenu de use : l'icône suit donc la couleur de texte de son parent, ainsi que toute valeur de color définie sur le svg englobant. En CSS, la largeur et la hauteur l'emportent sur les attributs, ce qui permet à la taille de suivre celle de la police.

Même origine uniquement

Les navigateurs ne suivent une référence use vers un fichier que si celui-ci se trouve sur la même origine que la page. Un sprite hébergé sur un CDN comme jsDelivr, ou sur un autre sous-domaine de votre site, n'affiche rien, et des en-têtes CORS ne changent pas la donne. Servez le sprite depuis votre propre origine, ou récupérez-le une fois et insérez-le dans la page :

inline-sprite.js
// jsDelivr sends CORS headers, so fetch works where <use> across origins does not.
const res = await fetch('https://cdn.jsdelivr.net/npm/glyphfox@1/dist/sprite.svg');
const holder = document.createElement('div');
holder.setAttribute('aria-hidden', 'true');
holder.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden';
holder.innerHTML = await res.text();
document.body.prepend(holder);

// now <use href="#gf-home"></use> works anywhere on the page

Intégrer le sprite dans la page

Un sprite inline, ce sont les mêmes symboles placés une seule fois dans le HTML, généralement juste après <body>. Les références n'utilisent alors que le fragment (#gf-home) : aucune requête supplémentaire n'est nécessaire et les icônes s'affichent dès le premier rendu. En contrepartie, chaque page transporte à nouveau les symboles : intégrez donc un petit sous-ensemble plutôt que le jeu complet.

layout.html
<body>
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
       style="position:absolute;width:0;height:0;overflow:hidden">
    <symbol id="gf-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">...</symbol>
    <symbol id="gf-search" viewBox="0 0 24 24" ...>...</symbol>
  </svg>

  <a href="/"><svg class="icon" aria-hidden="true"><use href="#gf-home"></use></svg> Home</a>
</body>

Cachez le sprite avec une taille nulle et un positionnement absolu, pas avec display:none. Certains navigateurs cessent d'afficher les masques contenus dans un SVG caché, or le style solid s'en sert pour découper ses lignes intérieures.

Générer un sous-ensemble avec la CLI

glyphfox build génère un sprite qui ne contient que les icônes que vous listez, rendues avec les props que vous passez. Les noms peuvent venir de la ligne de commande ou d'un fichier contenant un nom par ligne ; les espaces, les virgules et les commentaires # y sont autorisés.

terminal
npx glyphfox build home search settings bell user --type sprite --out public/icons
npx glyphfox build --from icons.txt --type sprite --weight 1.5 --out public/icons
npx glyphfox build --from icons.txt --type sprite --variant solid --out public/icons/solid
icons.txt
# app shell
home, search, settings, bell, user
menu x chevron-down

# badges and fusions work too
file+check
cloud&lock:overlap

Les noms simples conservent leur id : home devient gf-home. Les spécifications avec un badge ou une fusion reçoivent un id dans lequel les caractères spéciaux sont remplacés : file+check devient gf-file_check et cloud&lock:overlap devient gf-cloud__lock-overlap. Ouvrez une fois le fichier généré pour copier les id dont vous avez besoin.

Générer le sprite à la taille d'affichage vous apporte aussi la compensation optique des composants : --size 16 applique le trait légèrement plus épais que glyphfox utilise pour les icônes de 16 pixels, si bien qu'un sprite destiné à des tableaux denses paraît aussi affirmé que le reste de l'interface.

La même commande avec --type css génère à la place une feuille de style de classes de masque (<span class="gf gf-home">), pour les sites qui veulent des classes d'icônes sans Tailwind CSS. Le guide Tailwind CSS explique le comportement des icônes en masque.

Options de génération

Un sprite est statique : les props communes deviennent donc des options de génération. Voici celles qui comptent pour un sprite ; chaque option a la même signification que la prop du même nom dans le tableau des props.

OptionEffet sur les symboles
--type spritegénère sprite.svg (le type par défaut) ; css et package sont les autres types
--out DIRdossier de sortie, le dossier courant par défaut
--from FILElit les noms depuis un fichier
--weight Népaisseur du trait de 1 à 3, inscrite dans chaque symbole
--size Nla taille d'affichage en pixels, pour la compensation optique du trait en dessous de 24
--corners sharpjointures et extrémités vives
--variant duotone, --variant solidle style de tous les symboles
--color C, --secondary-color Ccouleurs fixes au lieu de currentColor
--secondary-opacity Nopacité du calque duotone ou du remplissage solid
--rotate DEG, --flip-x, --flip-yfige une rotation ou un effet miroir dans les symboles

Ce que le CSS peut et ne peut pas changer

Ce que dessine use est une copie du symbole. Le CSS appliqué au svg englobant ou à l'élément use n'atteint cette copie que par héritage, c'est pourquoi color fonctionne. Ce n'est pas le cas des propriétés que le symbole définit lui-même : chaque symbole porte stroke-width, stroke-linecap et stroke-linejoin sous forme d'attributs, et ceux-ci l'emportent sur toute valeur héritée de l'extérieur. Définir stroke-width sur .icon n'a aucun effet.

Choisissez l'épaisseur, les coins et le style au moment de générer le sprite, et générez deux sprites si vous avez besoin de deux épaisseurs. Pour contrôler par instance l'épaisseur, une seconde couleur ou le mouvement, affichez plutôt du SVG inline avec un composant ou toSvg.

Les animations de l'icône entière fonctionnent toujours, car elles agissent sur l'élément englobant : .icon.spin { animation: spin 1s linear infinite } fait tourner une icône de sprite comme n'importe quel autre élément.

Accessibilité

Une icône de sprite est un svg englobant que vous écrivez vous-même : c'est donc à vous d'en définir la sémantique. Masquez les icônes décoratives avec aria-hidden="true". Pour une icône qui porte du sens à elle seule, faites de l'élément englobant une image dotée d'un nom :

status.html
<svg class="icon" role="img" aria-label="Payment failed">
  <use href="/icons/sprite.svg#gf-circle-alert"></use>
</svg>

Les boutons qui ne contiennent qu'une icône ont besoin du libellé sur le bouton lui-même, comme l'explique le guide d'accessibilité.

Mise en cache

Un sprite externe représente une seule requête, partagée par toutes les pages. Servez-le avec une longue durée de cache et une empreinte dans le nom du fichier, comme sprite.3f9a1c.svg : un nouveau build change ainsi l'URL, au lieu d'attendre l'expiration des caches. Les bundlers s'en chargent pour vous lorsque vous importez le fichier. Chaque URL de sprite distincte n'est téléchargée qu'une fois par page, quel que soit le nombre d'icônes qui y font référence.

Pièges courants

Un sprite sur une autre origine
Un use externe qui pointe vers un CDN ou un autre sous-domaine affiche une boîte vide ; le navigateur se contente de signaler la requête bloquée dans la console. Hébergez le fichier sur l'origine de la page ou intégrez-le directement dans celle-ci.
Calques duotone dans le sprite complet
Les 42 icônes dotées d'un calque duotone dessiné à la main le conservent dans glyphfox/sprite.svg, sous forme de remplissage translucide dans la couleur courante. Si vous voulez des contours purs, générez un sous-ensemble : le style par défaut outline omet ce calque, et --variant duotone le conserve.
Masques dans les sprites cachés ou externes
Les symboles solid utilisent un mask pour découper leurs lignes intérieures. Les masques peuvent ne pas s'afficher lorsque le sprite est caché avec display:none, et la prise en charge des masques dans un fichier de sprite externe varie selon les navigateurs. Testez le style solid dans vos navigateurs cibles, ou intégrez ce sprite dans la page.
États vivants dans un sprite
Les préréglages comme spin ou draw sont limités à une classe posée sur le svg racine, qu'un symbole ne conserve pas : --animate n'a donc aucun effet visible dans un sprite. Animez l'élément englobant avec votre propre CSS, ou utilisez du SVG inline.
Identifiants en conflit
Un sprite inline ajoute à la page des id comme gf-home. Deux sprites inline contenant la même icône, ou l'un de vos propres éléments portant le même id, rendent les références ambiguës. N'intégrez qu'un seul sprite par page.

Questions fréquentes

Quand un sprite est-il préférable aux composants ?

Sur les sites rendus côté serveur ou statiques sans framework JavaScript, et sur les pages qui répètent de nombreuses icônes : les symboles sont téléchargés une seule fois et chaque utilisation ne coûte que quelques octets de balisage. Les composants l'emportent quand les icônes changent selon l'état, se transforment, s'animent ou ont besoin d'une seconde couleur, car un sprite est figé au moment du build.

Puis-je référencer directement le sprite sur jsDelivr ?

Pas avec use, car les navigateurs bloquent les références externes vers d'autres origines. Récupérez le fichier et insérez-le dans la page, comme indiqué plus haut, ou copiez-le sur votre propre serveur.

Icônes de ce guide