Curseurs✨ Premium

Trail Particles

Chaque mousemove lâche deux points violets de 3 à 9 px soumis à une gravité de 0,05 px par image : ils dérivent, retombent, rétrécissent et s'éteignent en 0,55 à 1,1 s — une boucle requestAnimationFrame qui détruit et recrée tous les points à chaque image.

JSCanvasrequestAnimationFrame

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

Passez la souris — hero d'un studio de motion design — Trail Particles exemple 1

① Passez la souris — hero d'un studio de motion design

QuandPage d'accueil d'un studio créatif : accroche, sous-titre et un bouton, sur un fond sombre qui occupe tout l'écran.
PourquoiLa traînée qui retombe illustre le métier — du mouvement — sans vidéo ni bibliothèque. Sur un hero plein écran, la zone est grande et la chute sous le pointeur a de la place ; le texte reste lisible parce que les points sont petits et meurent en une seconde.
RéglagesPalette réduite aux deux teintes de la marque, une particule par événement (f < 1) pour alléger, décroissance .02 + .02 × Math.random() pour une traînée plus courte (0,4 à 0,8 s).
Tableau blanc — application de dessin ou de brainstorming — Trail Particles exemple 2

② Tableau blanc — application de dessin ou de brainstorming

QuandCanevas d'une application collaborative, encadré d'un en-tête et d'une barre d'outils.
PourquoiLe point qui suit le stylet donne un retour immédiat sur une surface autrement vide, sans gêner : les particules sont en pointer-events: none, le clic passe au canevas. Ici overflow: hidden sur la zone est indispensable, la barre d'outils la borde.
RéglagesDiamètre 2 à 5 px (2 + 3 × Math.random()), gravité supprimée (vy += 0) pour des points qui flottent au lieu de tomber, frottement .95 pour qu'ils s'arrêtent vite.
Écran titre — jeu ou application ludique — Trail Particles exemple 3

③ Écran titre — jeu ou application ludique

QuandÉcran d'accueil d'un jeu : logo, mention « Nouvelle partie », score et niveau en coin, décor spatial.
PourquoiDes étincelles qui suivent la main avant même le premier clic disent « ça réagit » : c'est la promesse d'un jeu. Les violets et le halo s'accordent à un décor de nuit ; la gravité fait retomber les points comme des braises.
RéglagesTrois particules par événement (f < 3), diamètre 4 à 12 px, gravité .1 pour une chute franche, blanc ajouté à la palette pour des étincelles.

Comment ça marche

Le script parcourt chaque [data-trail-particles] et lui pose un écouteur mousemove sans limiteur de débit : à chaque événement, deux particules sont poussées dans un tableau (for (f = 0; f < 2; f++)). Chacune naît aux coordonnées du pointeur relatives à la zone (getBoundingClientRect) avec une vitesse horizontale 3 × (Math.random() − .5), soit −1,5 à +1,5 px par image, une vitesse verticale 3 × (Math.random() − .5) − 1, soit −2,5 à +0,5 px par image (le biais de −1 la fait partir vers le haut), une vie life = 1, une décroissance decay = .015 + .015 × Math.random(), un diamètre 3 + 6 × Math.random() (3 à 9 px) et une couleur tirée parmi six violets et indigos (#a78bfa, #6366f1, #8b5cf6, #c084fc, #e879f9, #818cf8).

La boucle l() tourne en requestAnimationFrame tant que le tableau n'est pas vide (un drapeau o évite de la lancer deux fois). À chaque image elle applique la physique : x += vx, y += vy, puis vy += .05 (gravité), vx *= .99 (frottement) et life −= decay. Avec une décroissance de 0,015 à 0,03 par image, une particule vit 33 à 67 images, soit 0,55 à 1,1 s à 60 images par seconde. Sur un trajet d'une seconde mesuré dans Chromium, 18 particules coexistaient au plus.

Le rendu est refait de zéro à chaque image : tous les .trail-particle sont retirés (querySelectorAll(...).forEach(e => e.remove())), puis un div neuf est créé par particule vivante — left/top en px, width/height = size × life (le point rétrécit en mourant), opacity = life, background de sa couleur et un halo box-shadow: 0 0 2·size px de la même couleur. Mesuré : aucun nœud ne survit d'une image à la suivante. Le CSS compagnon .trail-particle (position: absolute, border-radius: 50%, pointer-events: none) était absent du code vendu jusqu'au 5 septembre 2026 : sans lui, les div restaient en flux, invisibles.

La zone .trail-zone fait 100% de large sur 300px de haut (dégradé radial #1a1a3e → #0a0a1a, cursor: crosshair), en position: relative pour servir de repère aux left/top. Elle est en overflow: hidden : les particules qui retombent sont découpées au bord — sans cette découpe, une particule émise à 6 px du bord bas retombait jusqu'à 121 px sous la zone (mesuré), par-dessus le contenu voisin. Les coordonnées placent le coin haut-gauche du point sous le pointeur, pas son centre. Enfin l'écouteur mouseleave est une fonction vide : rien n'est nettoyé à la sortie, les points meurent d'eux-mêmes.

Accessibilité

  • prefers-reduced-motion absent du code : la traînée se produit quelle que soit la préférence système. Entourez l'ajout de l'écouteur d'un if (!matchMedia('(prefers-reduced-motion: reduce)').matches) — la seule règle CSS .trail-particle { display: none } cacherait les points mais laisserait la boucle calculer et recréer des div pour rien.
  • L'effet est exclusivement souris : aucun mousemove au clavier, et au tactile le navigateur n'en émet au plus qu'un au toucher (deux points, aussitôt éteints). La zone est un div non focalisable qui ne porte aucune information : gardez-la décorative, ne placez jamais de consigne ou de contenu que seule la traînée révélerait.
  • Contraste : le texte d'invite fourni, rgba(255,255,255,.15) en 12,8 px, mesure 1,5:1 sur le fond — lisible par personne. S'il doit être lu, passez à rgba(255,255,255,.5) (5,3:1). Les particules sont non textuelles : la moins contrastée, #6366f1, reste à 4,4:1 sur #0a0a1a, au-dessus du seuil de 3:1 des éléments graphiques.
  • Lecteurs d'écran : les div injectés et retirés jusqu'à 60 fois par seconde sont vides, donc muets, mais ils font battre l'arbre d'accessibilité. Posez aria-hidden="true" sur la zone [data-trail-particles] pour que tout ce qui y naît soit ignoré d'un bloc. Le pointeur natif reste visible (cursor: crosshair, pas cursor: none) : aucun repère n'est retiré à l'utilisateur.
  • Intégration : la zone doit garder position: relative et une hauteur explicite — avec height: 100% dans un parent sans hauteur, elle rendait 0 px et la souris n'y entrait jamais ; le code vendu fixe 300px. L'overflow: hidden de la zone découpe au bord les points qui retombent : conservez-le si des éléments sont placés sous la zone, et ne passez à visible que pour laisser les points déborder sur un fond vide. Aucune remise à zéro n'est nécessaire à la sortie : la boucle s'arrête seule quand la dernière particule meurt.

Compatibilité navigateur

Code ES5 (var, function, aucune fonction fléchée) : il ne requiert que requestAnimationFrame, getBoundingClientRect, box-shadow et border-radius. Zéro dépendance.

Chrome 24+✓ Complet
Firefox 23+✓ Complet
Safari 6.1+✓ Complet
Edge 12+✓ Complet
Mobile iOS✓ Statique (pas de mousemove)
Android Chrome✓ Statique (pas de mousemove)

Sans JavaScript, il reste une zone au dégradé radial et son texte d'invite : rien ne bouge, rien ne manque au contenu. Sans box-shadow (navigateurs très anciens), les points sont nets, sans halo.

Le code

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

index.html — structure
<div class="trail-zone" data-trail-particles>
  <div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;color:rgba(255,255,255,0.15);font-size:0.8rem;">Déplacez le curseur ici</div>
</div>
🔒 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
Particules par événement (JS — f < 2) 2 Nombre de points créés à chaque mousemove. Il n'y a aucun limiteur de débit : à 120 événements par seconde, 2 donne 240 points par seconde. 1 allège de moitié ; au-delà de 3, ajoutez un seuil temporel comme dans Particle Trail.
Gravité (JS — e.vy += .05) .05 px/image² Accélération verticale ajoutée à chaque image. 0 : les points flottent et dérivent ; .15 : ils tombent lourdement. Négative, ils s'envolent.
Frottement (JS — e.vx *= .99) .99 Multiplie la vitesse horizontale à chaque image. .9 freine net les points, 1 les laisse filer en ligne droite. Ne s'applique pas à la verticale.
Décroissance (JS — decay: .015 + .015 × Math.random()) .015 à .03 par image Détermine la durée de vie : 1 / decay images, soit 0,55 à 1,1 s à 60 images/s. Doublez les deux termes pour une traînée deux fois plus courte. La taille et l'opacité suivent life, donc la décroissance règle aussi la vitesse du rétrécissement.
Diamètre (JS — size: 3 + 6 × Math.random()) 3 à 9 px Taille de naissance ; le point mesure ensuite size × life. Le halo box-shadow vaut deux fois le diamètre : agrandir les points agrandit la lueur.
Palette (JS — tableau a) #a78bfa, #6366f1, #8b5cf6, #c084fc, #e879f9, #818cf8 Couleur tirée au hasard par point, appliquée au fond et au halo. Une seule entrée = traînée unie ; vérifiez le contraste sur votre fond (3:1 pour un élément graphique).
Vitesse initiale (JS — 3 × (Math.random() − .5) et − 1) ±1,5 px/image, biais vertical −1 Le facteur 3 règle la dispersion, le − 1 la poussée initiale vers le haut. − 3 fait jaillir les points comme une fontaine ; 0 les laisse tomber depuis le pointeur.

FAQ

Deux choses coûtent : la création de deux points par mousemove sans limiteur, et surtout le rendu, qui détruit et recrée chaque div à chaque image (mesuré : aucun nœud conservé entre deux images). Avec 18 points vivants, c'est 18 suppressions et 18 créations 60 fois par seconde. Deux corrections simples : ajouter un seuil temporel (if (Date.now() − dernier < 30) return) comme dans Particle Trail, et réutiliser les nœuds — garder el dans l'objet particule et n'écrire que style.left/top/width/height/opacity. La boucle, elle, s'arrête d'elle-même dès que le tableau est vide : au repos, l'effet ne consomme rien.
Pas avec le code livré : .trail-zone est en overflow: hidden, et les points que la gravité fait retomber sont découpés au bord bas — sans cette découpe, une émission à 6 px du bord descendait jusqu'à 120 px sous la zone (mesuré). Si la zone a des coins arrondis, le découpage les respecte. Pour laisser les points déborder — sur un hero plein écran, par exemple — passez la zone en overflow: visible : les particules étant en pointer-events: none, elles n'interceptent rien, mais elles se dessinent par-dessus les éléments suivants.
Plusieurs zones, oui, sans rien changer : le script fait querySelectorAll('[data-trail-particles]') et donne à chacune son tableau et sa boucle. Toute la page est possible en posant l'attribut sur un conteneur en position: relative qui l'englobe — mais les left/top sont relatifs à ce conteneur, pas à la fenêtre : s'il défile, les points nés en haut restent en haut. Pour une traînée fixe à l'écran, il faudrait passer les particules en position: fixed et utiliser clientX/clientY sans soustraire le rectangle de la zone.