Cursor Ripple
Un verrou de 100 ms sur le mousemove cadence la naissance des ondulations : à chaque impulsion autorisée, un élément naît aux coordonnées du pointeur, s'anime via une classe CSS, et se détruit automatiquement 600 ms plus tard — jusqu'à six anneaux coexistent dans le DOM à vitesse soutenue.
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
Avant toute création, le code passe par un filtre temporel. À chaque événement mousemove, il compare Date.now() à la variable lastRippleTime : si l'écart est inférieur à 100 ms (rippleThrottle), le gestionnaire retourne immédiatement. Ce délai règle le rythme de l'effet — il cadence les anneaux bien plus que la vitesse du curseur. À mouvement lent, les rondelles naissent espacées et s'épanouissent individuellement. À mouvement rapide, le filtre autorise dix impulsions par seconde au maximum. Quand le curseur s'immobilise, aucun mousemove ne circule : l'effet s'éteint de lui-même, sans nettoyage à prévoir.
À chaque impulsion passée le verrou, un <div> reçoit deux classes : custom-cursor cursor-ripple. La première fixe les propriétés de base des curseurs, notamment position: fixed ; la seconde porte les @keyframes — agrandissement de l'anneau et effacement de son opacité. L'élément est inséré dans document.body, pas dans la zone d'écoute. Ce choix est cohérent avec les coordonnées utilisées : e.clientX et e.clientY sont dans le viewport, et position: fixed les interprète dans ce même repère. Un décalage de −10 px sur chaque axe compense la moitié du diamètre implicite de l'anneau pour centrer son origine sur le hotspot du curseur.
Une fois inséré, l'élément est autonome. Un setTimeout de 600 ms programme son retrait via ripple.remove() — aucun registre des éléments en vol, aucune interruption si le curseur sort de la zone. Durée de vie (600 ms) divisée par throttle (100 ms) : au maximum, six anneaux coexistent simultanément dans le document. À vitesse soutenue, ils se chevauchent en traîne d'ondes ; à vitesse faible, chaque anneau achève seul sa course avant le suivant. Contrairement à une traîne de curseur, les bagues restent fixées à leur point de naissance — elles se propagent depuis l'endroit où le curseur était, pas depuis où il est.
Accessibilité
- prefers-reduced-motion : absent du code. La classe
cursor-ripplene vérifie aucune préférence système. Conditionne la création côté JavaScript avecwindow.matchMedia('(prefers-reduced-motion: reduce)').matchesavant l'insertion, ou enveloppe les @keyframes dans@media (prefers-reduced-motion: no-preference)côté CSS. - Sur écran tactile,
mousemovene se déclenche pas : aucune bague ne naît, aucun nettoyage n'est nécessaire. Pour des ondulations au tap, un écouteurtouchstartdistinct convient — mais chaque doigt générant son propre événement, un geste multi-touch crée plusieurs ondulations simultanées. - Les éléments atterrissent dans
<body>sansaria-hidden— ces nœuds dynamiques peuvent être annoncés par le lecteur d'écran. Ajouteripple.setAttribute('aria-hidden', 'true')après lecreateElementpour les exclure de l'arbre d'accessibilité. - Avec
position: fixed, le scroll ne neutralise pas les ondulations existantes : elles restent ancrées au viewport jusqu'à l'expiration du timer. Réduis leur durée de vie ou confine-les dans la zone parente (voir Personnalisation) si la page scrolle.
Compatibilité navigateur
Repose sur les fonctions fléchées et const/let (ES2015), Date.now() (ES5), classList et setTimeout. Le plancher est dicté par les fonctions fléchées : IE 11 échoue avant le premier mousemove. Côté CSS, position: fixed et @keyframes sont acquis depuis 2012.
Sans JavaScript, aucun élément n'est créé et la zone reste inerte — le repli est intégré à la conception. Sur IE 11, l'erreur de syntaxe sur les fonctions fléchées bloque le script ; si IE 11 est une cible, transpile-le : la logique n'a aucune autre dépendance.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
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 |
|---|---|---|
| Délai de cadence (rippleThrottle = 100) | 100 ms | Comparé à l'écart entre deux appels successifs à Date.now(). À 50 ms, le nombre maximal d'anneaux simultanés double (douze pour une durée de vie de 600 ms). À 200 ms, il descend à trois — chaque bague a la place de s'épanouir seule. |
| Durée de vie de l'anneau (setTimeout 600) | 600 ms | La durée du setTimeout et celle de l'animation @keyframes doivent rester alignées. Si l'animation dure 400 ms mais le timer 600 ms, l'élément reste 200 ms invisible dans le DOM. Descends sous 300 ms pour des éclats vifs ; monte à 900 ms pour des anneaux qui se fondent lentement. |
| Décalage de centrage (−10 px sur left et top) | −10 px | Compense la moitié du diamètre visuel de l'anneau. Si tu changes la taille de .cursor-ripple — par exemple width: 40px — le décalage doit passer à −20. Un mauvais décalage déplace le centre de la bague par rapport au hotspot du curseur. |
| Classe CSS de l'anneau (cursor-ripple) | cursor-ripple | Toute la partie visuelle — couleur, taille initiale, keyframes d'agrandissement et de fondu — vit dans cette classe. Crée des variantes (cursor-ripple--click, cursor-ripple--large) et alterne la classe selon le contexte sans modifier le JavaScript. |
| Cible d'insertion (document.body) | document.body | Insérer dans body avec position: fixed ancre les anneaux au viewport. Pour les confiner dans un conteneur scrollable, insère-les dans la zone parente (position: relative) et calcule des coordonnées relatives : e.clientX - rect.left et e.clientY - rect.top. Remplace aussi fixed par absolute dans la classe CSS. |
| Événement déclencheur (mousemove) | mousemove | Remplace par click pour des ondulations uniquement au clic — le throttle devient inutile, chaque clic produit exactement un anneau. Combine les deux en attachant deux écouteurs distincts avec des classes différentes : mouvement pour des petits anneaux fins, clic pour un grand anneau d'impact. |
FAQ
<div> animés par CSS sont négligeables. Sur une page lourde en transforms, monte le throttle à 150 ms (quatre éléments simultanés au lieu de six).e.clientX et e.clientY sont des coordonnées viewport que position: fixed interprète dans ce même repère. Changer la cible sans adapter les coordonnées décale les anneaux par rapport au curseur. Pour les confiner dans la zone, calcule des coordonnées relatives et remplace fixed par absolute dans la classe CSS.position: fixed, le scroll ne les déplace pas — elles restent ancrées au viewport jusqu'à expiration du setTimeout. Et le code ne pose aucun mouseleave : une bague créée en bord de zone finit ses 600 ms même si le curseur est sorti. Pour interrompre les animations en cours à la sortie de zone, collecte les id retournés par setTimeout et appelle clearTimeout dans un écouteur mouseleave.