Text Whirlpool
Les caractères s'envolent en orbite circulaire puis convergent vers le texte lisible d'un seul mouvement élastique — 100 % transitions CSS, zéro canvas.
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
La fonction utilitaire splitChars() décompose l'attribut data-text en autant de <span class="char"> que de caractères. L'init JS calcule ensuite pour chaque span une position angulaire sur un cercle de rayon ≈ 77 px selon la formule angle = phase + index × (2π / n) (n = nombre de caractères, phase ≈ 14,4°), convertie en coordonnées cartésiennes via Math.cos / Math.sin.
Chaque char reçoit un style inline position:absolute; transform:translate(x,y) rotate(14.4deg); opacity:0.424; transition:none. Le transition:none inline est crucial : il bloque toute animation au chargement, même si la règle CSS .kt-whirlpool .char { transition:1.2s cubic-bezier(.34,1.56,.64,1) } est déjà en place. Le parent .kt-whirlpool est rendu position:relative par le JS, ce qui le positionne à zéro volume dans son conteneur flex-centré — les chars orbitent donc autour du centre visuel de la zone.
Pour lancer l'animation, deux requestAnimationFrame imbriqués suppriment l'override transition:none (via c.style.removeProperty('transition')) puis ajoutent la classe .active au conteneur. Les règles .kt-whirlpool.active .char { position:relative!important; transform:translate(0,0) rotate(0deg)!important; opacity:1!important } s'appliquent alors : chaque char quitte son orbite et rejoint son emplacement naturel dans le flux de texte. Le cubic-bezier(.34,1.56,.64,1) produit un léger dépassement puis rebond — effet ressort (spring).
Un clic sur le conteneur relance le cycle : retrait de .active, réapplication des styles inline circulaires avec transition:none, puis re-déclenchement via double-rAF. L'effet est entièrement DOM — aucun canvas, aucune librairie externe.
Accessibilité
- prefers-reduced-motion : non implémenté dans le code fourni. Solution minimale à ajouter :
@media (prefers-reduced-motion: reduce) { .kt-whirlpool .char { transition: none !important; } }et déclencher directement.activesans attendre le double-rAF. - Aucun attribut
aria-*dans la structure HTML de base — ajouterrole="img"etaria-label="[votre texte]"sur le conteneur si l'effet tient lieu de titre décoratif. - Le texte est dans le DOM sous forme de
<span>lisibles : les lecteurs d'écran accèdent au contenu textuel indépendamment de l'animation. - L'interaction clavier n'est pas nativement supportée (conteneur
<div>non focusable) — ajoutertabindex="0"et un listenerkeydown(Entrée/Espace) pour rendre le replay accessible. - Aucun
aria-live: le changement d'état visuel n'est pas annoncé aux technologies d'assistance.
Compatibilité navigateur
Nécessite les transitions CSS et requestAnimationFrame — supportés dans tous les navigateurs modernes. Zéro dépendance externe.
Sans transitions CSS, les caractères passent directement à leur état lisible final sans animation — aucune erreur JS, le texte reste affiché et lisible.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="demo-preview">
<div class="kt-text kt-whirlpool" id="whirlpool1" data-text="VORTEX" style="cursor: pointer;">
<!-- Un <span class="char" data-char="V">V</span> par caractère
positionné en orbite par l'init JS (position:absolute + translate) -->
</div>
<span class="demo-hint">Cliquer pour relancer</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 |
|---|---|---|
| data-text | "VORTEX" | Texte à animer — modifiez directement l'attribut HTML. Le nombre de caractères ajuste automatiquement l'espacement angulaire (360° / n). |
| font-size sur .kt-text | 2.2rem | Taille des caractères. Une taille plus grande appelle un rayon orbital plus large — augmentez la constante RADIUS dans le JS d'initialisation en proportion. |
| color sur .kt-text | #fff | Couleur des caractères en état lisible. L'opacité en orbite (0.424) est imposée par le style inline du JS init et s'applique quelle que soit la couleur. |
| transition dans .kt-whirlpool .char | 1.2s cubic-bezier(.34, 1.56, .64, 1) | Durée et courbe de la convergence. Augmentez à 1.8s pour un mouvement lent et cinematique, réduisez à 0.6s pour une animation vive. Modifiez le cubic-bezier pour plus ou moins de rebond (2e valeur > 1 = dépassement). |
| RADIUS (constante JS init) | ~77 | Rayon du cercle orbital en pixels. Calibrez selon le nombre de caractères et la font-size — un mot de 4 lettres en 5rem appelle un rayon de 100–120 px. |
| opacity: 0.424 (style inline JS init) | 0.424 | Opacité des caractères pendant l'orbite, avant convergence. 0 = invisible au départ (surgissement soudain), 0.7 = semi-visible (effet plus doux). |
| rotate(14.4deg) (style inline JS init) | 14.4deg | Rotation uniforme appliquée à tous les caractères en orbite. 0deg = lettres droites pendant l'orbite, 45deg = caractères fortement inclinés. |
FAQ
<span> DOM, des transitions CSS et quelques lignes de JS pour le calcul des positions orbitales. Zéro dépendance npm, zéro canvas. Compatible avec tout framework : enveloppez le conteneur dans un composant, déclenchez l'ajout de .active depuis votre logique et retirez-le pour réinitialiser.data-text directement dans le HTML (data-text="STUDIO"). Le JS init lit cet attribut, recrée les <span class="char"> et recalcule les positions angulaires automatiquement — aucune modification du script n'est nécessaire..kt-whirlpool par élément (et non par variable globale) : donnez un id distinct à chaque conteneur. Chacun dispose alors de son propre état .active, de son listener clic isolé et de ses chars indépendants. Aucune variable d'état n'est partagée entre instances.