Stroke Animation
Remplissage progressif gauche–droite d'un texte outlined via pseudo-élément et clip-path animé — 100 % CSS, zéro JavaScript requis.
Vous aurez accès à la démo interactive avec un compte gratuit.
Cet effet fait partie d'Effect.Labs — 811 effets vanilla, une partie gratuite, une partie premium. Texte comprend 56 effets, dont 4 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
L'effet repose sur deux couches superposées : l'élément .text-stroke porte -webkit-text-stroke: 2px #6366f1 et -webkit-text-fill-color: transparent — le texte apparaît donc en outline creux. Un pseudo-élément ::before est généré par content: attr(data-text) et positionné en absolute sur left: 0; top: 0, formant une copie exacte du texte en couleur pleine par-dessus l'outline.
La révélation s'obtient par clip-path animé : ::before démarre à clip-path: inset(0 100% 0 0) (rognage 100 % depuis la droite — le texte rempli est entièrement masqué) et l'animation @keyframes strokeFill le fait progresser vers clip-path: inset(0) (aucun rognage — texte pleinement visible). Le résultat visuel est un wipe gauche–droite qui révèle progressivement la couleur à travers l'outline.
La propriété animation-direction: alternate inverse le cycle à chaque itération : le texte se remplit, puis se vide de droite à gauche, puis se remplit à nouveau. Passer à animation-direction: normal avec animation-iteration-count: 1 et animation-fill-mode: forwards produit une révélation unique, non répétée — le texte reste rempli à la fin.
Le JS embarqué définit une fonction helper splitChars(el) qui découpe le texte de l'élément en <span class="char"> individuels pour des animations caractère par caractère avec décalage. Elle n'est pas appelée au démarrage — l'animation par défaut s'applique au mot entier via CSS. La constante document.getElementById('fadeInText') retourne null dans la configuration fournie (pas d'id fadeInText dans le HTML) : aucun effet de bord JS à l'initialisation.
Accessibilité
- prefers-reduced-motion absent : aucune media query n'est présente dans le code fourni — l'animation tourne inconditionnellement. Correctif recommandé :
@media (prefers-reduced-motion: reduce) { .text-stroke::before { animation: none; clip-path: inset(0) } }(le texte reste visible, immobile). - Le texte est du contenu DOM réel (textContent de l'élément
<span>) — lu nativement par les lecteurs d'écran sans attribut ARIA supplémentaire. - La couleur de remplissage
#6366f1sur fond#0a0a0fatteint un ratio de contraste ≈ 3,5:1 — conforme WCAG AA pour les grands textes (≥ 24 px / weight ≥ 700), insuffisant pour un corps de texte courant. Pendant l'état « outline seul », le contraste de l'outline fin peut être en-dessous du seuil. - Aucune interaction clavier — l'effet est 100 % CSS, aucun focus implicite généré.
- Aucun
aria-labelnirolen'est posé sur l'élément. Ajouter unaria-labelau wrapper si le texte est très stylisé ou si la couleur seule le rend ambigu (ex. texte sur fond complexe).
Compatibilité navigateur
S'appuie sur -webkit-text-stroke, -webkit-text-fill-color et clip-path: inset() — supportés dans tous les navigateurs modernes depuis 2019. La syntaxe -webkit- est conservée par convention mais fonctionne sans préfixe sur Firefox et Edge.
Sans support de <code>-webkit-text-fill-color</code>, le texte s'affiche avec sa couleur CSS héritée (pas d'outline transparent). Sans <code>clip-path</code>, le pseudo-élément <code>::before</code> est entièrement visible dès le chargement — le texte reste lisible, l'animation de révélation n'a pas lieu.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="demo-preview">
<!-- data-text doit correspondre exactement au textContent de l'élément
(les deux sont lus par les lecteurs d'écran) -->
<span
class="demo-text text-stroke"
data-text="VOTRE TEXTE"
>VOTRE TEXTE</span>
</div>
HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| -webkit-text-stroke sur .text-stroke | 2px #6366f1 | Épaisseur (px) et couleur (hex) de l'outline. 1px pour un trait fin, 4px pour un affichage très grand. La couleur peut différer du fill pour un effet bicolore. |
| -webkit-text-fill-color sur .text-stroke::before | #6366f1 | Couleur du remplissage. Doit généralement correspondre à la couleur de l'outline, mais peut être différente pour un effet intentionnel (ex. stroke blanc, fill doré). |
| animation-duration sur .text-stroke::before | 2s | Durée d'un demi-cycle (remplissage ou vidage). 1s = rapide et percutant, 4s = lent et cinématique. |
| animation-direction sur .text-stroke::before | alternate | Valeur normal pour un wipe unique gauche–droite sans retour. Combiner avec animation-iteration-count: 1 et animation-fill-mode: forwards. |
| animation-timing-function sur .text-stroke::before | ease-in-out | Courbe de vitesse du wipe. linear = progression régulière, cubic-bezier(.4,0,.2,1) = départ lent puis accélération dramatique. |
| font-size sur .demo-text | 2.5rem | Taille du texte. L'effet est plus lisible à partir de 2rem — en dessous, l'outline fine peut rendre la lecture difficile. |
| data-text sur l'élément | STROKE | Texte affiché ET dupliqué dans content: attr(data-text) sur le pseudo-élément. Doit correspondre au textContent — les deux sont lus par les outils d'accessibilité. |
| clip-path initial sur .text-stroke::before | inset(0 100% 0 0) | Définit le bord de départ du wipe. inset(0 0 0 100%) inverse la direction (révélation droite–gauche). inset(100% 0 0 0) produit un wipe de bas en haut. |
FAQ
splitChars(el) pour animer les lettres une par une, mais elle n'est pas appelée au démarrage. Vous pouvez omettre ce bloc JS sans aucune conséquence sur l'animation de base.animation: 2s ease-in-out 0s infinite alternate par animation: 2s ease-in-out 0s 1 normal none forwards strokeFill. Les valeurs clés : 1 (une seule itération), normal (remplissage gauche→droite sans retour), forwards (le texte reste rempli à la fin de l'animation).splitChars(el) fournie dans le JS : elle découpe le texte en <span class="char"> et retourne la NodeList. Appliquez ensuite un animation-delay croissant sur chaque .char via chars.forEach((c, i) => c.style.animationDelay = i * 0.12 + 's'). Ajoutez position: relative à .char pour que le ::before CSS se positionne correctement sur chaque lettre.