Cursor Trail Effect
Cinq points fixes de 8 px, d'opacité 0,7 à 0,3, qui se poursuivent en chaîne : chacun rejoint le précédent à 30 %, 25 %, 20 %, 15 % puis 10 % de l'écart par frame. Sur un saut de 456 px la traînée s'étire jusqu'à 406 px et le dernier point met 79 frames à se recoller. Le curseur natif est masqué dans la zone.
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. Curseurs comprend 26 effets, dont 1 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
Le HTML fournit cinq div vides #trailV2-1 à #trailV2-5, tous .cursor-trail-v2 — position: fixed, 8 × 8 px, #6366f1, border-radius: 50%, pointer-events: none, z-index: 9999, display: none — groupés dans .cursor-trail-v2-set pour que :nth-child(1) à (5) leur donne une opacité décroissante : 0,7, 0,6, 0,5, 0,4, 0,3 (mesurées). Aucune transition CSS, à dessein : la position est réécrite à chaque frame par le JS.
La zone #zone-trail-v2 écoute mouseenter (classe cursor-active — dont la règle cursor: none !important est bien livrée, mesuré cursor: none — et display: block sur les cinq), mouseleave (l'inverse) et mousemove, qui ne fait que mémoriser trailV2X/Y. Tout le mouvement est dans une boucle requestAnimationFrame : pour le point i, la cible est le pointeur si i === 0, sinon la position du point i − 1 ; speed = 0.3 − i × 0.05 ; pos.x += (target.x − pos.x) × speed ; puis left = pos.x − 4.
C'est une chaîne d'approches exponentielles : le premier point comble 30 % de son retard par frame, le dernier 10 %, et chacun ne vise que son prédécesseur. Mesuré à 120 Hz après un saut de 456 px : écart au pointeur à la frame 5 = 54, 177, 315, 407 et 445 px pour les points 1 à 5 ; étirement maximal de la traînée 406 px à la frame 7 ; retour à moins de 1 px aux frames 17, 26, 37, 52 et 79 (≈ 0,65 s pour la queue). La longueur de la traînée dépend donc de la vitesse de la main, pas d'une durée fixe.
Les positions sont initialisées à {x: 0, y: 0} : à la première entrée, les cinq points partent du coin haut-gauche de la fenêtre (mesuré à la frame 0 pour un pointeur en 152, 150 : point 1 en 46, 45 … point 5 en 0, 0). La boucle ne s'arrête jamais — 120 appels rAF/s mesurés hors survol, points cachés — soit dix écritures de style par frame pour rien. Et le lissage est compté en frames : sur un écran 60 Hz, le rattrapage prend deux fois plus de temps en millisecondes qu'à 120 Hz.
Accessibilité
- Contraste des points (WCAG 1.4.11, seuil 3:1) :
#6366f1à 0,7 d'opacité sur#1a1a24= 2,5:1, à 0,3 = 1,4:1 — sous le seuil pour tous. La traînée est décorative, mais aveccursor: nonele premier point est le seul repère du pointeur : passez-le àopacity: 1(3,9:1) ou choisissez#a5b4fc(4,9:1 à 0,7). cursor: nonemasque la flèche alors que le point de tête suit avec retard (30 % par frame) : la position exacte du pointeur n'est jamais affichée. Sur une zone qui contient des cibles à cliquer, gardez la flèche (retirez la règle.cursor-active) et laissez la traînée l'accompagner.- Tactile : mesuré avec émulation tactile, un tap dans la zone affiche les cinq points figés et laisse
cursor-activeposée même après un tap hors zone (pas demouseleave). Conditionnez l'initialisation àmatchMedia('(hover: hover) and (pointer: fine)').matches. - prefers-reduced-motion absent : une traînée qui glisse derrière la main est une animation déclenchée par l'interaction (WCAG 2.3.3). Sous
@media (prefers-reduced-motion: reduce), masquez les points 2 à 5 (.cursor-trail-v2:not(:first-child) { display: none !important }) ou mettez toutes les vitesses à 1. - Lecteurs d'écran et clavier : cinq
divvides,pointer-events: none, rien n'est annoncé — ajoutezaria-hidden="true"sur le groupe.cursor-trail-v2-set. Aucun déclenchement au clavier.z-index: 9999: les points passent au-dessus des modales.
Compatibilité navigateur
Requiert ES2015 (const/let, fonctions fléchées, Array.prototype.some/map), classList, requestAnimationFrame et les variables CSS de la zone. Zéro dépendance.
Sans JavaScript, les cinq points restent en display: none et la flèche système est conservée. Le JS s'arrête proprement si l'un des cinq ids manque (if (trailV2Elements.some(el => !el)) return) : supprimer un point dans le HTML désactive tout l'effet, il faut aussi raccourcir le tableau.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="cursor-zone" id="zone-trail-v2">
Survolez cette zone
<span class="info-text">Trainee fluide en 5 points</span>
</div>
<div class="cursor-trail-v2-set">
<div class="cursor-trail-v2" id="trailV2-1"></div>
<div class="cursor-trail-v2" id="trailV2-2"></div>
<!-- … 5 au total, une par point de traînée … -->
</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 |
|---|---|---|
Vitesses (JS — speed = 0.3 - (i * 0.05)) |
0.3 → 0.1 | Part de l'écart comblée par frame pour chaque point. Baisser la pente (0.04) allonge la traînée ; monter le premier terme (0.5) colle le point de tête au pointeur. |
Nombre de points (HTML — div ; JS — tableau trailV2Elements ; CSS — :nth-child) |
5 | Les trois doivent concorder : le JS s'arrête si un id manque. Avec 8 points, prolongez la formule (0.3 − i × 0.03) pour que la dernière vitesse reste positive. |
Taille (CSS — .cursor-trail-v2 8px) + offset (JS — pos.x - 4) |
8 px / −4 | Couplées : l'offset doit rester la moitié de la taille pour centrer les points. Des tailles décroissantes par :nth-child donnent une traînée effilée (garder l'offset du plus grand). |
| Couleur (CSS — .cursor-trail-v2 background) | #6366f1 | Une seule couleur pour les cinq points. Par :nth-child, vous pouvez dégrader la teinte (indigo → rose) en plus de l'opacité. |
| Opacités (CSS — .cursor-trail-v2:nth-child(n)) | 0,7 · 0,6 · 0,5 · 0,4 · 0,3 | Extinction le long de la traînée. Le point 1 à 1 donne un repère net (3,9:1) ; des paliers plus serrés (0,9 → 0,1) rendent la queue quasi invisible. |
Position initiale (JS — map(() => ({x: 0, y: 0}))) |
0, 0 | Cause du départ depuis le coin haut-gauche. Dans mouseenter, réinitialisez trailV2X/Y et chaque pos sur e.clientX/Y. |
Arrêt de la boucle (JS — animateTrailV2) |
permanente | Stockez l'identifiant de requestAnimationFrame, annulez-le dans mouseleave avec cancelAnimationFrame, relancez dans mouseenter : zéro travail hors survol. |
FAQ
mouseenter, les points s'affichent là où ils sont, dans le coin haut-gauche, puis rejoignent le pointeur en cascade — mesuré : point 1 en (46, 45) et point 5 encore en (0, 0) à la frame 0 pour un pointeur en (152, 150). Ajoutez dans mouseenter : trailV2X = e.clientX; trailV2Y = e.clientY; trailV2Positions.forEach(p => { p.x = e.clientX; p.y = e.clientY; }).transition CSS sur left/top — relancée 60 fois par seconde, elle ferait ramper la queue (c'est l'effet mesuré sur Dot Follower, fx-0197, où elle double le temps de rattrapage). Écrivez plutôt transform: translate3d() au lieu de left/top pour rester sur le compositeur.</body>, dans leur conteneur .cursor-trail-v2-set (nécessaire aux :nth-child). position: fixed avec left = clientX suppose la fenêtre comme repère : tout ancêtre porteur de transform, filter, perspective, will-change: transform ou contain: paint devient le bloc conteneur et décale la traînée de sa propre position. Le code livré place les div dans la boîte de démo : déplacez-les à la racine dès l'intégration.