Icônes animées
glyphfox anime les icônes de deux façons. Les états vivants sont des préréglages, comme draw, pop ou shake, qui se jouent en réponse à un déclencheur. Les morphs transforment une icône en une autre. Les deux fonctionnent dans tous les paquets et se désactivent d'eux-mêmes pour les personnes qui demandent un mouvement réduit.
Mise en place
Rien de plus à installer : l'animation fait partie des props communes. Les exemples ci-dessous utilisent glyphfox/react ; les props portent les mêmes noms dans Vue, Svelte, Angular et le web component, où elles prennent la forme d'attributs en kebab-case.
npm install glyphfox
Un état vivant, ce sont quelques lignes de CSS embarquées dans le SVG lui-même, dans un élément <style> dont la portée est limitée à une classe générée pour cette icône. C'est pourquoi la même animation fonctionne dans un composant, dans la chaîne renvoyée par toSvg, dans une data URI ou dans un fichier exporté, sans aucune feuille de style à importer.
Les sept préréglages
Chaque préréglage est pensé pour des icônes plutôt que pour des éléments quelconques. draw trace les traits dans l'ordre où ils ont été dessinés et fait apparaître le calque de remplissage en fondu à la fin. shake oscille depuis le haut, comme une cloche suspendue à son crochet. Les autres animent l'icône entière autour de son centre.
| Préréglage | Mouvement | Durée | En boucle par défaut |
|---|---|---|---|
draw | trace chaque trait, l'un après l'autre | 1100 ms | non |
pop | grandit à partir de 55 % avec un léger dépassement | 520 ms | non |
wiggle | pivote à gauche et à droite en s'amortissant | 720 ms | non |
spin | tour complet à vitesse constante | 1000 ms | oui |
pulse | rétrécit et s'estompe légèrement, puis revient | 1500 ms | oui |
bounce | saute vers le haut, puis se pose avec deux rebonds plus petits | 900 ms | non |
shake | oscille depuis le haut avec une amplitude décroissante | 950 ms | non |
import { LoaderCircleIcon, SparklesIcon, BellIcon, HeartIcon } from 'glyphfox/react'; export function Presets({ hasNews, liked }) { return ( <> <LoaderCircleIcon animate="spin" /> <SparklesIcon animate="draw" /> <BellIcon animate="shake" trigger="state" active={hasNews} /> <HeartIcon animate="pop" trigger="click" variant={liked ? 'solid' : 'outline'} /> </> ); }
duration et delay s'expriment en millisecondes. loop accepte true pour une boucle infinie ou un nombre d'exécutions ; spin et pulse s'exécutent en boucle, sauf si vous passez loop={false}. Un draw en boucle alterne le sens : l'icône se dessine puis s'efface, au lieu de revenir brusquement au début.
Déclencheurs
La prop trigger détermine quand un préréglage se joue :
mount, la valeur par défaut, se joue au premier rendu de l'icône.hoverse joue tant que le pointeur survole l'icône ou son parent direct : une icône placée dans un bouton s'anime donc au survol du bouton.clickse rejoue à chaque clic sur l'icône.statese joue tant queactivevaut true. C'est le déclencheur à utiliser pour tout ce que décide votre application : une nouvelle notification, un échec d'enregistrement, un envoi de fichier terminé.
import { BellIcon } from 'glyphfox/react'; export function NotificationsButton({ unread }) { return ( <button type="button" aria-label={`Notifications, ${unread} unread`}> <BellIcon animate="shake" trigger="state" active={unread > 0} loop={2} /> </button> ); }
Dans un balisage statique, les déclencheurs correspondent à de simples points d'accroche CSS. Une animation state se joue tant que l'élément svg porte la classe gf-on. Le HTML rendu côté serveur et les chaînes issues de toSvg se pilotent donc avec une seule ligne de script : svg.classList.toggle('gf-on', unread > 0). hover ne demande aucun script.
Transformer une icône en une autre
Le moteur de morph aplatit les deux icônes en brins, les apparie par une affectation optimale, les rééchantillonne au même nombre de points, puis interpole. Les brins sans partenaire rétrécissent et s'estompent. Deux icônes quelconques peuvent se transformer l'une en l'autre ; dix paires sont réglées à la main pour sembler conçues plutôt que calculées :
| Départ | Arrivée |
|---|---|
| menu | x |
| play | pause |
| plus | check |
| sun | moon |
| eye | eye-off |
| bell | bell-ring |
| heart | heart en style plein |
| copy | check |
| lock | unlock |
| wifi | wifi-off |
Il existe trois façons de demander un morph. MorphIcon se transforme chaque fois que sa prop name change. Placée sur n'importe quelle icône, la prop morphTo transforme l'icône en une cible quand son déclencheur s'active, puis lui rend sa forme d'origine quand il s'arrête. Les hooks et les stores (useMorph dans React et Vue, morphStore dans Svelte) vous fournissent les images de l'animation pour votre propre rendu.
import { useState } from 'react'; import { MorphIcon, MenuIcon, registerIcons } from 'glyphfox/react'; import { iconCopy, iconCheck, iconX } from 'glyphfox'; // Morph targets are looked up by name: register their data once. registerIcons([iconCopy, iconCheck, iconX]); export function CopyButton({ text }) { const [copied, setCopied] = useState(false); const copy = async () => { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1500); }; return ( <button type="button" onClick={copy}> <MorphIcon name={copied ? 'check' : 'copy'} duration={300} /> {copied ? 'Copied' : 'Copy'} </button> ); } export function MenuButton({ open, onToggle }) { return ( <button type="button" aria-expanded={open} aria-label="Menu" onClick={onToggle}> <MenuIcon morphTo="x" trigger="state" active={open} /> </button> ); }
Un morph dure 450 ms, sauf si vous passez duration. Avec trigger="state", l'icône suit active dans les deux sens, ce qui convient aux éléments à bascule comme un menu, un champ de mot de passe (eye vers eye-off) ou un bouton de lecture.
Les mêmes morphs dans Vue, Svelte et le web component
<script setup> import { ref } from 'vue'; import { MorphIcon, registerIcons } from 'glyphfox/vue'; import { iconPlay, iconPause } from 'glyphfox'; registerIcons([iconPlay, iconPause]); const playing = ref(false); </script> <template> <button type="button" :aria-label="playing ? 'Pause' : 'Play'" @click="playing = !playing"> <MorphIcon :name="playing ? 'pause' : 'play'" /> </button> </template>
<script> import { Icon, morphStore, registerIcons } from 'glyphfox/svelte'; import { iconSun, iconMoon } from 'glyphfox'; registerIcons([iconSun, iconMoon]); const icon = morphStore('sun', { duration: 400 }); let dark = $state(false); </script> <button type="button" aria-label="Dark theme" aria-pressed={dark} onclick={() => { dark = !dark; icon.set(dark ? 'moon' : 'sun'); }}> {#if $icon}<Icon icon={$icon.icon} variant={$icon.variant} />{/if} </button>
<script src="https://cdn.jsdelivr.net/npm/glyphfox@1"></script> <button type="button" id="menu" aria-expanded="false" aria-label="Menu"> <glyph-fox name="menu" morph duration="300"></glyph-fox> </button> <script> const button = document.getElementById('menu'); const icon = button.querySelector('glyph-fox'); button.addEventListener('click', () => { const open = button.getAttribute('aria-expanded') !== 'true'; button.setAttribute('aria-expanded', String(open)); icon.setAttribute('name', open ? 'x' : 'menu'); }); </script>
Avec l'attribut morph, <glyph-fox> anime chaque changement de name. Le build CDN contient déjà toutes les icônes : aucun enregistrement n'y est nécessaire.
Morphs sans JavaScript
toSvg avec morphTo renvoie un SVG dont les tracés portent la forme de départ et passent à la cible grâce à une transition CSS sur d. Les navigateurs qui ne prennent pas en charge d en CSS effectuent à la place un fondu enchaîné vers la cible exacte. Le résultat est une simple chaîne, prête pour les templates côté serveur, les sites statiques ou un CMS.
import { toSvg } from 'glyphfox'; // Morphs while the pointer is over the icon or its direct parent. const menu = toSvg('menu', { morphTo: 'x', trigger: 'hover', duration: 300 }); // Morphs while the svg has the class gf-on: toggle it from any script. const play = toSvg('play', { morphTo: 'pause', trigger: 'state' });
La CLI génère le même fichier : npx glyphfox morph menu x --trigger hover --out menu.svg. Seul toSvg résout une cible donnée par son nom ; renderToString de glyphfox/core attend des données d'icône pour morphTo.
Classes d'animation Tailwind CSS
Les icônes rendues avec le plugin Tailwind CSS sont des masques et non du SVG : elles acceptent donc des animations de l'élément entier (gf-spin, gf-pulse, gf-pop, gf-wiggle, gf-bounce et gf-shake), avec le même minutage que les préréglages. Elles fonctionnent avec des variantes comme hover:gf-wiggle. L'animation draw et les morphs nécessitent du SVG inline.
Mouvement réduit
Chaque préréglage embarque une règle prefers-reduced-motion: reduce qui désactive son animation, le morph en CSS pur bascule sans transition, et les morphs JavaScript des composants passent directement à la forme finale. Vous n'avez pas besoin de vérifier ce réglage vous-même. Les personnes qui l'ont activé voient tout de même l'état final : le cœur plein, le x d'un menu ouvert, la coche après une copie.
Le mouvement réduit n'est pas la seule raison de garder les animations courtes. Le mouvement attire l'œil : réservez donc les animations en boucle aux opérations en cours, comme un loader-circle qui tourne pendant l'exécution d'une requête, et arrêtez-les une fois le travail terminé. Le guide d'accessibilité approfondit la question du mouvement et du sens.
Exporter des fichiers animés
L'outil de personnalisation de l'explorateur d'icônes exporte n'importe quelle icône dotée d'un préréglage en SVG animé, en GIF ou en vidéo WebM, avec exactement le même mouvement : les images du GIF et du WebM sont échantillonnées à partir du minutage du préréglage. Utilisez le SVG animé partout où le SVG est accepté, et le GIF ou le WebM pour les présentations, les outils de documentation et les messageries qui suppriment le SVG.
Performances
- Les préréglages autres que
drawn'animent quetransformetopacity, que les navigateurs traitent sans recalcul de mise en page.drawanimestroke-dashoffset, ce qui redessine l'icône à chaque image : sans problème pour quelques icônes, mais à éviter dans les longues listes. - Les morphs des composants reposent sur
requestAnimationFrameet refont le rendu de l'icône à chaque image pendant toute la durée du morph. Quelques-uns à la fois ne coûtent presque rien ; des centaines lancés en même temps, si. - Les noms de classes générés sont des hachages des réglages : des icônes identiques produisent donc un balisage identique, et les rendus serveur et client concordent.
Props d'animation
Voici les props qui contrôlent le mouvement. Toutes les autres props communes, comme variant ou weight, peuvent s'y combiner.
| Prop | Type | Par défaut | Description |
|---|---|---|---|
animate | draw, pop, wiggle, spin, pulse, bounce, shake | aucun | Préréglage d'état vivant. |
trigger | mount, hover, click, state | mount | Moment où l'animation ou le morph se joue. |
active | boolean | false | L'état pour le déclencheur state. |
duration | number (ms) | selon le préréglage | Durée de l'animation ou du morph. |
delay | number (ms) | 0 | Délai de l'animation ou du morph. |
loop | boolean, number | false | Répète à l'infini ou un certain nombre de fois (spin et pulse bouclent par défaut). |
morphTo | name, data | aucun | Cible du morph ; le déclencheur décide quand elle s'affiche. |
Pièges courants
- Cibles de morph qui n'affichent rien
- Les cibles de morph, comme les noms passés à
MorphIconet àmorphStore, sont recherchées par nom. Un nom dont les données n'ont jamais été importées ni enregistrées est chargé à la demande, et dans un build de production Vite ce chargement n'a pas lieu, car Vite laisse intact l'import dynamique à chemin variable situé dansnode_modules. La cible reste vide. Enregistrez avecregisterIconschaque nom vers lequel une icône se transforme. Importer un composant commeCheckIconuniquement pour son effet de bord ne sert à rien, car le tree shaking supprime tout import jamais utilisé. - Survol et clic en dehors de l'icône
- Sur du SVG inline, un préréglage au survol réagit aussi au parent direct, grâce au CSS. Ce n'est pas le cas de tout ce qui repose sur des événements :
trigger="click"et le déclencheur hover d'un morph écoutent les événements de l'icône elle-même, etglyph-foxne réagit qu'au pointeur sur l'élément lui-même. Un clic dans le padding d'un bouton ne les atteint pas. Gardez l'état dans le bouton et transmettez-le à l'icône avectrigger="state"etactive. - Animations au montage qui se rejouent
- Une animation
mountse joue à chaque montage de l'icône : quand une liste est rendue à nouveau avec de nouvelles clés, quand la route change, et surglyph-foxchaque fois que l'un de ses attributs change. Si une icône ne doit s'animer qu'une fois, utiliseztrigger="state"et décidez dans votre code du moment où elle se joue. - Animations d'état qui ne s'arrêtent jamais
- Un préréglage avec
loopcontinue de se jouer tant queactivereste à true. Remettezactiveà false une fois l'événement traité, ou donnez un nombre àloop. - Balisage statique et clics
- Dans le HTML produit par
toSvg, rien n'écoute les clics. Utiliseztrigger: 'state'et basculez la classegf-ondepuis votre propre gestionnaire d'événements.
Questions fréquentes
Les icônes animées nécessitent-elles JavaScript ?
Pas les états vivants : le CSS se trouve dans le SVG, si bien qu'une icône rendue côté serveur ou statique s'anime au montage et au survol sans aucun script. Le morph en CSS pur de toSvg fonctionne lui aussi sans framework. Les morphs des composants, comme MorphIcon, utilisent JavaScript pour calculer les images.
Une icône peut-elle se transformer et jouer un préréglage en même temps ?
Oui. animate et morphTo sont des props indépendantes, si bien qu'un cœur peut jouer l'animation pop au clic tout en se transformant en cœur plein : <HeartIcon animate="pop" morphTo="heart/solid" trigger="click" />.