SVG Morphing Loaders
Trois indicateurs SVG de 54 px — blob (2,2 s), goutte (1,6 s), étoile (1,9 s) — dont le path se morphe en boucle par interpolation SMIL native, sans ligne de JavaScript.
Cet effet est gratuit — la catégorie Chargement en contient 29 au total, dont 4 gratuits. Effect.Labs regroupe 811 effets vanilla. Découvrir la catégorie →
Exemples d'usage
Traitements en cours
3 tâches en parallèle · ~2 min restantesOrbis
Espace de travail
Chargement de votre espace…
Application en cours de chargementComment ça marche
L'effet repose sur une .sml-row (flex, gap: 34px) qui aligne trois .sml-item (grid, place-items: center, gap: 8px). Chaque item contient deux SVG 54 × 54 px (viewBox="0 0 54 54") — .sml-anim pour la version animée, .sml-static pour le repli figé — et un .sml-label (0,62 rem, majuscules, letter-spacing: 0.14em, rgb(143, 143, 176)). Le Blob est rempli par un dégradé linéaire défini dans un <linearGradient id="smlGradA"> inline (#7c3aed → #db2777, violet-rose) ; la Goutte est en #5a9bff (bleu) et l'Étoile en #ffd166 (jaune). Le code livré est sans JavaScript : la bascule entre les deux SVG est entièrement CSS — .sml-item .sml-static { display: none } en rendu normal, et une media query prefers-reduced-motion qui inverse les deux affichages (détaillée dans la section accessibilité).
Chaque SVG animé contient un unique <animate attributeName="d"> qui interpole l'attribut d du <path> entre trois états (keyTimes="0;.5;1"), le premier et le dernier identiques — boucle symétrique. Le Blob oscille entre un ovale presque parfait et une goutte asymétrique à midi ; la Goutte entre une larme pointue vers le haut et un disque ; l'Étoile à cinq branches se rétrécit et se resserre. calcMode="spline" avec keySplines=".4 0 .2 1;.4 0 .2 1" (courbe de Bézier cubique) lisse les entrées et sorties de chaque transition — l'équivalent SMIL d'un ease-in-out. repeatCount="indefinite" maintient la boucle sans interruption. L'interpolation opère directement sur les coordonnées des points de contrôle Bézier du path ; c'est le navigateur qui calcule chaque frame.
Les durées 2,2 s, 1,6 s et 1,9 s n'ont pas de rapport rationnel simple entre elles : leur plus petit commun multiple dépasse 50 secondes, ce qui rend toute resynchronisation imperceptible — les trois formes respirent de manière indépendante, jamais en phase. La règle de masquage est écrite .sml-item .sml-static { display: none }, et non .sml-static { display: none }, pour une raison de spécificité CSS : la feuille contient .sml-item svg { display: block } (une classe + un type, spécificité 0-1-1), qui s'applique aux six SVG. Un simple .sml-static (0-1-0) perdrait ce duel quelle que soit sa position dans la feuille — à nombre de classes égal, le sélecteur de type fait la différence — et chaque item empilerait alors ses deux SVG (54 + 8 + 54 px) avant le label. Avec deux classes (0-2-0), la règle l'emporte et chaque item ne rend qu'un SVG : 54 px, 8 px d'écart, puis le label.
Accessibilité
- Les trois
.sml-animet.sml-staticportentaria-hidden="true": aucun texte alternatif n'est annoncé. Si l'ensemble représente un état de chargement, ajoutez un conteneur parentrole="status" aria-live="polite"avec un texte masqué — par exemple<span class="sr-only">Chargement en cours…</span>. Sans cela, les lecteurs d'écran ignorent totalement l'indicateur. - prefers-reduced-motion pris en charge : le CSS livré contient
@media (prefers-reduced-motion: reduce) { .sml-item .sml-anim { display: none } .sml-item .sml-static { display: block } }. Quand la préférence est active, le SVG animé n'est plus rendu et le SVG statique — la forme au repos, identique au premier état de chaque<animate>— prend sa place : un seul SVG par indicateur dans les deux cas. Subtilité à conserver si vous fusionnez ce CSS dans une feuille plus large :@median'ajoute aucune spécificité, et.sml-item .sml-static { display: block }a exactement la même (0-2-0) que la règle de masquage ; c'est l'ordre dans la feuille qui tranche, le bloc@mediadoit donc rester après elle. - Les libellés sont en français dans le HTML (« Blob », « Goutte », « Étoile ») et chaque
<span class="sml-label">porte en plus un attributdata-en(« Blob », « Drop », « Star ») que la version anglaise du site utilise. Dans votre projet, remplacez directement le texte selon la langue de l'interface — l'attributdata-enest inerte sans script qui le lise, vous pouvez le retirer. Si les libellés sont redondants avec le contexte de la page, ajoutezaria-hidden="true"sur chaque span. - Contraste des indicateurs (WCAG 1.4.11, éléments graphiques, seuil 3:1) sur fond
#0a0a0f: Goutte (#5a9bff) 7,1:1 ; Étoile (#ffd166) 13,7:1 ; Blob, dégradé de 3,5:1 (violet#7c3aed) à 4,3:1 (rose#db2777) — les trois passent le seuil. Sur fond clair, le rapport s'inverse : l'Étoile tombe à 1,4:1 sur blanc et la Goutte à 2,8:1, sous le seuil ; foncez-les (par exemple#b45309, 5,0:1, et#2563eb, 5,2:1) ou gardez un fond sombre. Le Blob reste conforme (5,7:1 et 4,6:1).
Compatibilité navigateur
Requiert l'élément SMIL <animate> avec calcMode="spline", linearGradient SVG et display: grid. Zéro dépendance, aucun JavaScript.
Sans SMIL (Edge ≤ 18 / IE), les <animate> sont ignorés — et comme les paths animés n'ont pas d'attribut d propre (c'est l'animation qui le fournit), ils ne dessinent rien : l'indicateur laisse une case vide de 54 px au-dessus de son label, les .sml-static restant masqués hors mouvement réduit. Ces navigateurs ne sont plus maintenus ; si vous devez quand même les couvrir, copiez sur chaque path animé l'attribut d de son jumeau statique : SMIL l'écrase quand il est disponible, et il reste affiché sinon.
Le code
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="sml-row">
<div class="sml-item">
<svg class="sml-anim" width="54" height="54" viewBox="0 0 54 54" aria-hidden="true">
<path fill="url(#smlGradA)">
<animate attributeName="d" dur="2.2s" repeatCount="indefinite" calcMode="spline" keyTimes="0;.5;1" keySplines=".4 0 .2 1;.4 0 .2 1" values="M27 5 C41 5 49 13 49 27 C49 41 41 49 27 49 C13 49 5 41 5 27 C5 13 13 5 27 5 Z;M27 8 C44 3 51 16 46 29 C42 44 30 52 18 46 C6 41 2 26 9 16 C14 8 20 10 27 8 Z;M27 5 C41 5 49 13 49 27 C49 41 41 49 27 49 C13 49 5 41 5 27 C5 13 13 5 27 5 Z">
</animate>
</path>
</svg>
<svg class="sml-static" width="54" height="54" viewBox="0 0 54 54" aria-hidden="true">
<path fill="url(#smlGradA)" d="M27 5 C41 5 49 13 49 27 C49 41 41 49 27 49 C13 49 5 41 5 27 C5 13 13 5 27 5 Z">
</path>
</svg>
<span class="sml-label" data-en="Blob">Blob</span>
</div>
<div class="sml-item">
<svg class="sml-anim" width="54" height="54" viewBox="0 0 54 54" aria-hidden="true">
<path fill="#5a9bff">
<animate attributeName="d" dur="1.6s" repeatCount="indefinite" calcMode="spline" keyTimes="0;.5;1" keySplines=".4 0 .2 1;.4 0 .2 1" values="M27 6 C34 16 44 24 44 33 C44 43 36 49 27 49 C18 49 10 43 10 33 C10 24 20 16 27 6 Z;M27 10 C38 10 46 18 46 28 C46 39 38 46 27 46 C16 46 8 39 8 28 C8 18 16 10 27 10 Z;M27 6 C34 16 44 24 44 33 C44 43 36 49 27 49 C18 49 10 43 10 33 C10 24 20 16 27 6 Z">
</animate>
</path>
</svg>
<svg class="sml-static" width="54" height="54" viewBox="0 0 54 54" aria-hidden="true">
<path fill="#5a9bff" d="M27 6 C34 16 44 24 44 33 C44 43 36 49 27 49 C18 49 10 43 10 33 C10 24 20 16 27 6 Z">
</path>
</svg>
<span class="sml-label" data-en="Drop">Goutte</span>
</div>
<div class="sml-item">
<svg class="sml-anim" width="54" height="54" viewBox="0 0 54 54" aria-hidden="true">
<path fill="#ffd166">
<animate attributeName="d" dur="1.9s" repeatCount="indefinite" calcMode="spline" keyTimes="0;.5;1" keySplines=".4 0 .2 1;.4 0 .2 1" values="M27 6 L33 20 L48 21 L37 31 L41 46 L27 38 L13 46 L17 31 L6 21 L21 20 Z;M27 12 L31 22 L42 23 L34 30 L36 41 L27 36 L18 41 L20 30 L12 23 L23 22 Z;M27 6 L33 20 L48 21 L37 31 L41 46 L27 38 L13 46 L17 31 L6 21 L21 20 Z">
</animate>
</path>
</svg>
<svg class="sml-static" width="54" height="54" viewBox="0 0 54 54" aria-hidden="true">
<path fill="#ffd166" d="M27 6 L33 20 L48 21 L37 31 L41 46 L27 38 L13 46 L17 31 L6 21 L21 20 Z">
</path>
</svg>
<span class="sml-label" data-en="Star">Étoile</span>
</div>
<svg width="0" height="0" aria-hidden="true">
<defs>
<linearGradient id="smlGradA" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#7c3aed">
</stop>
<stop offset="1" stop-color="#db2777">
</stop>
</linearGradient>
</defs>
</svg>
</div>
.sml-row {
display: flex;
gap: 34px;
align-items: center;
justify-content: center;
}
.sml-item {
display: grid;
place-items: center;
gap: 8px;
}
.sml-item svg {
display: block;
}
.sml-label {
font-size: 0.62rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: rgb(143, 143, 176);
}
.sml-item .sml-static {
display: none;
}
@media (prefers-reduced-motion: reduce) {
.sml-item .sml-anim {
display: none;
}
.sml-item .sml-static {
display: block;
}
}
// Effet CSS pur — pas de JavaScript nécessaire
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Durées des animations (dur sur chaque <animate>) |
2.2s (Blob) / 1.6s (Goutte) / 1.9s (Étoile) | Allongez les trois durées pour un morphing plus lent et contemplatif, réduisez-les pour un état d'urgence. Évitez les rapports simples (1:2, 2:3) pour conserver le désynchronisme organique. |
Couleur de la Goutte et de l'Étoile (attribut fill sur <path>) |
#5a9bff (Goutte) / #ffd166 (Étoile) | Chaque path est indépendant. Couleur unie, gradient défini en <defs> ou currentColor pour hériter de la couleur du texte environnant. |
Couleurs du Blob (stops du <linearGradient id="smlGradA">) |
#7c3aed → #db2777 (violet → rose) | Modifiez les attributs stop-color des deux <stop>. Pour une couleur unie sur le Blob, remplacez fill="url(#smlGradA)" par une valeur hexadécimale directe. |
Taille des indicateurs (attributs width/height sur les <svg>) |
54 px | Le viewBox restant à 0 0 54 54, la forme s'adapte proprement à toute taille. 32 px pour un bouton, 72 à 96 px pour un splash screen. Pas besoin de modifier les paths. |
Courbe de transition (keySplines sur chaque <animate>) |
.4 0 .2 1;.4 0 .2 1 | Deux valeurs cubiques (une par segment 0→0,5 et 0,5→1). 0 0 1 1;0 0 1 1 = interpolation linéaire (mécanique) ; .8 0 .2 1;.8 0 .2 1 = très prononcé, long arrêt aux deux extrêmes. |
Écart entre indicateurs (gap sur .sml-row) |
34px | Compressez à 16-20 px pour tenir dans un bouton ou une barre étroite ; élargissez à 48-64 px pour un panneau ou un splash screen. |
Libellés (contenu des <span class="sml-label">) |
Blob / Goutte / Étoile — attribut data-en : Blob / Drop / Star | Remplacez le texte par des verbes d'action (Analyser, Générer, Vérifier) ou par la langue de votre interface — l'attribut data-en ne sert qu'à la version anglaise d'Effect.Labs, retirez-le ou alignez-le. Pour masquer les libellés : .sml-label { display: none }. |
FAQ
Pourquoi les trois durées 2,2 s, 1,6 s et 1,9 s ne sont-elles jamais des multiples les unes des autres ?
Pour éviter la resynchronisation périodique. Deux indicateurs dont les durées ont un rapport rationnel simple (ex. 1,6 s et 3,2 s) s'alignent toutes les 3,2 s : on perçoit clairement le cycle. Avec 2,2 / 1,6 / 1,9 s, le premier retour exact au même état simultané nécessite plus de 50 s — bien au-delà du temps d'attention. Le triplet paraît alors respirer librement. Pour un seul indicateur ou pour des durées identiques, ce raisonnement ne s'applique pas.
Puis-je placer deux instances de ce triplet sur la même page ?
Pas sans modification. L'id smlGradA doit être unique dans le document ; la deuxième instance référence url(#smlGradA) qui résout sur la première définition — les deux Blobs utilisent le même dégradé, ce qui fonctionne visuellement mais est fragile. Pour deux instances indépendantes, suffixez les ids (smlGradA-1, smlGradA-2) et mettez à jour les attributs fill correspondants.
En quoi cet effet diffère-t-il de Morphing Blob (fx-0506) et Morphing Shape (fx-0431) ?
Technique et nombre. fx-0506 et fx-0431 animent la propriété CSS border-radius sur un seul <div> — la mécanique CSS ne peut pas produire une étoile ou une larme, seulement des rectangles à coins arrondis. fx-0777 anime l'attribut SVG d via SMIL <animate> sur trois paths distincts : les formes (goutte, étoile) peuvent être quelconques, et l'interpolation est assurée par le moteur de rendu SVG sans JavaScript. Le triplet désynchronisé n'existe pas dans les deux autres effets.
Pourquoi chaque indicateur embarque-t-il deux SVG ? Puis-je n'en garder qu'un ?
Le second SVG (.sml-static) est le repli sans mouvement. Une animation SMIL ne se pilote pas depuis le CSS : animation-play-state ou animation: none ne lui font rien, seul svg.pauseAnimations() en JavaScript l'arrête. Sans script, la seule façon de respecter prefers-reduced-motion est donc de retirer le SVG animé du rendu (display: none) et d'afficher à sa place une copie figée — c'est ce que fait la media query livrée. Si vous ne gardez que le SVG animé, appelez pauseAnimations() sur chaque <svg> dès le chargement quand matchMedia('(prefers-reduced-motion: reduce)').matches est vrai : la forme se fige sur son premier état — même résultat, au prix de quelques lignes de JavaScript.