Curseurs✨ Premium

Cursor Trail

Dix points en chaîne qui suivent le curseur par interpolation : chaque point rattrape son prédécesseur à 30 % par frame, si bien que la queue ondule loin derrière — elle s'allonge dans les virages et se rétracte à l'arrêt.

JSTrail

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

Menu plein écran — navigation créative — Cursor Trail exemple 1

① Menu plein écran — navigation créative

QuandUn menu overlay couvrant toute la fenêtre, avec de grands liens typographiques et un curseur personnalisé prévu dans la charte graphique.
PourquoiLa traînée accompagne physiquement le déplacement du pointeur — elle suit, elle ne rayonne pas depuis un point fixe. Le sillage laissé derrière le curseur donne une sensation de vitesse et de matière qui renforce l'intention de navigation sans ajouter d'élément statique à la page.
Réglages5–6 points pour une queue courte et précise ; lerp à 0,4 pour qu'elle reste serrée derrière le curseur ; points blancs à faible opacité pour ne pas concurrencer la typographie.
Hero ambiante — fond interactif sombre — Cursor Trail exemple 2

② Hero ambiante — fond interactif sombre

QuandUn hero sombre où le visiteur est invité à promener sa souris sur un fond texturé ou illustré, sans élément cliquable dans la zone.
PourquoiDix points lerp produisent une ondulation organique selon la vitesse du geste : la queue s'étire dans les virages et se rétracte à l'arrêt. Cette dynamique de chaîne est impossible à reproduire avec une trace SVG — chaque maillon calcule sa position de façon autonome.
RéglagesLerp à 0,12 pour une traînée longue et flottante ; coefficient d'opacité à 0,06 pour que la queue reste visible loin derrière ; mix-blend-mode: screen sur les points pour s'intégrer au fond.
Onboarding guidé — tracer un chemin — Cursor Trail exemple 3

③ Onboarding guidé — tracer un chemin

QuandUn écran d'onboarding ou un tutoriel qui demande à l'utilisateur de faire passer son curseur sur une zone précise pour déclencher l'étape suivante.
PourquoiLa traînée visualise la trajectoire en cours de tracé : retour visuel immédiat qui confirme l'action sans alert ni toast. Plus convaincant qu'un changement de couleur de zone, car il montre le mouvement, pas seulement l'état.
Réglages15–18 points pour une queue persistante ; lerp à 0,2 pour que la queue reste visible longtemps après un changement de direction ; couleur de validation distincte de la couleur d'exploration pour signaler les zones activées.

Comment ça marche

Au démarrage, le script crée dix div (classe cursor-trail), les accroche à document.body et les stocke dans trailDots avec leurs coordonnées à (0, 0). Chaque point reçoit à la création une opacité figée — 1 − i × 0,1, de 1,0 à 0,1 — et une échelle fixe — scale(1 − i × 0,08), de 1,0 à 0,28. Ces valeurs ne changent plus. Tous restent en display: none jusqu'au mouseenter ; mouseleave les cache. La position du curseur est capturée dans trailX / trailY à chaque mousemove.

Toute la cinématique tient dans animateTrail(), lancée par requestAnimationFrame au démarrage et qui ne s'arrête jamais. À chaque frame, les dix points sont parcourus dans l'ordre. Le point 0 rejoint la position courante du curseur ; chaque suivant rejoint son prédécesseur. La formule est identique pour tous : dot.x += (prev.x − dot.x) × 0,3. Parcourir 30 % de l'écart restant par frame est une interpolation qui converge sans jamais dépasser la cible. Le retard se cumule de maillon en maillon : le dernier point n'a que (0,7)^9 ≈ 4 % de la réactivité de la tête.

Deux détails d'implémentation ont des conséquences directes. Les points sont accrochés à body, pas à la zone : leurs coordonnées (clientX / clientY) sont des positions dans la fenêtre, rien ne les clippe. Et la boucle rAF ne s'arrête pas : pendant que display: none les cache, les dix éléments continuent d'interpoler vers les dernières coordonnées connues — quand mouseenter les réaffiche, la traînée reprend sans à-coup. La position de chaque point est décalée de −5 px sur les deux axes pour centrer un point de 10 × 10 px.

Accessibilité

  • prefers-reduced-motion : absent du code. La boucle rAF tourne sans condition et cursor-active masque le curseur natif quoi qu'il arrive. Conditionne le lancement d'animateTrail() et l'ajout des écouteurs à window.matchMedia('(prefers-reduced-motion: reduce)') inversée — les utilisateurs qui ont activé cette préférence ne doivent pas perdre leur curseur.
  • Sur écran tactile, aucun mousemove ne circule et la traînée est inerte. Mais un touchstart peut déclencher un mouseenter synthétique sur certains navigateurs, activant cursor-active et masquant le curseur sans traînée visible. Conditionne les écouteurs à matchMedia('(hover: hover) and (pointer: fine)') pour bloquer cette émulation.
  • Plusieurs zones instanciées = autant de boucles rAF parallèles. Pour stopper la boucle hors survol, mémorise l'identifiant rAF, appelle cancelAnimationFrame() sur mouseleave et relance sur mouseenter. Pour plusieurs zones simultanées, une boucle globale unique gérant toutes les instances actives est plus propre.
  • Les dix points sont des div vides dans body, sans rôle ni texte. Ajoute aria-hidden="true" à chaque point pendant la boucle de création pour les exclure de l'arbre d'accessibilité et éviter qu'un lecteur d'écran ne les énumère dans la navigation.

Compatibilité navigateur

La limite est côté JavaScript : fonctions fléchées et template literal (pour l'attribut scale()). IE 11 s'arrête sur une erreur de syntaxe avant la création du premier point. Le CSS qui positionne les points (position: fixed, pointer-events: none) et masque le curseur (cursor: none) est acquis partout depuis 2012.

Chrome 49+✓ Complet
Firefox 44+✓ Complet
Safari 10+✓ Complet
Edge 15+✓ Complet
Mobile iOS✓ Inerte (tactile)
Android Chrome✓ Inerte (tactile)

Sans JavaScript, aucun point n'est créé et aucune traînée n'apparaît — repli sain, l'effet n'emporte rien de fonctionnel. Sur mobile, sans <code>mousemove</code>, les points restent en <code>display: none</code> et le curseur système s'affiche normalement. Si tu dois couvrir un moteur ES5, transpile le script : la logique n'a aucune autre dépendance.

Le code

Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :

index.html — structure
🔒 Débloquer le code complet — à partir de 2,99 € le 1er mois

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éfautEffet
Nombre de points (10 dans la boucle d'initialisation) 10 Longueur directe de la traînée. Chaque point est un div dans le DOM et un lerp par frame — reste sous 20. 4–5 = queue courte et serrée ; 15–18 = ondulation qui persiste longtemps après un virage.
Facteur d'interpolation (0,3 dans animateTrail) 0,3 Part du chemin parcourue par chaque point par frame. 0,1 = traînée très flottante, la queue persiste longtemps ; 0,5 = traînée tendue, la queue rattrape vite la tête. Commence par ce réglage avant de toucher au nombre de points.
Gradient d'opacité (coefficient 0,1 par rang) 1 − i × 0,1 Figé à la création, jamais mis à jour. 0,05 = fondu très progressif ; 0,15 = effacement brutal. Avec 10 points, ne dépasse pas 0,1 : au-delà, les derniers rangs sont invisibles dès la création.
Gradient de taille (coefficient 0,08 par rang) scale(1 − i × 0,08) Aussi figé à la création. 0,05 donne une queue à 55 % ; 0,12 donne le dernier point à 8 % — quasi invisible. Combine-le avec le gradient d'opacité pour moduler taille et lisibilité indépendamment.
Décalage de centrage (−5 dans le JS) −5 px sur les deux axes Position écrite comme dot.x − 5 et dot.y − 5 pour centrer un point de 10 × 10 px. Taille CSS à 8 px → décalage −4 ; à 16 px → décalage −8. Un décalage incorrect déporte toute la traînée du vrai pointeur.
Élément parent des points (document.body) document.body Les points utilisent clientX / clientY — coordonnées de fenêtre, correctes uniquement si le parent n'a pas de contexte de positionnement. Pour clipper la traînée à la zone, déplace l'appendChild vers la zone et bascule sur e.offsetX / e.offsetY avec overflow: hidden.

FAQ

Avec dix points, l'impact est imperceptible : dix lerps et dix écritures inline par frame sont négligeables. Le vrai risque est l'accumulation : plusieurs zones instanciées = autant de boucles rAF parallèles. La solution propre est une boucle globale unique. Pour stopper la boucle hors survol, mémorise l'identifiant rAF, appelle cancelAnimationFrame() sur mouseleave et relance sur mouseenter.
Parce que leurs coordonnées sont des clientX / clientY — des positions dans la fenêtre. Accrochés à body, ils se placent correctement sans calcul de décalage. Dans un parent avec son propre contexte de positionnement, l'origine change et tous les points dérivent. Pour les confiner à la zone, bascule sur e.offsetX / e.offsetY avec position: absolute dans un parent position: relative et overflow: hidden.
Toute la présentation passe par la classe cursor-trail en CSS : change border-radius pour passer du cercle au carré ; background pour une couleur pleine ou un dégradé radial ; box-shadow pour un halo. Si tu modifies la taille CSS, ajuste le décalage −5 en conséquence. Pour une couleur différente par rang, accède à trailDots[i].el pendant la boucle d'initialisation et pose la couleur directement sur l'élément.