Curseurs✨ Premium

Cursor Ripple Field

Au plus un anneau tous les 120 ms : un div de 2 px de bordure, teinte tirée entre 220° et 280°, qui grandit de 0 à 150 px en 1 s — largeur et scale animés ensemble, d'où un départ lent — pendant que son opacité s'éteint, puis se retire seul à animationend.

JSAnimationRipple

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

Respirez — hero d'une application de méditation ou de sommeil — Cursor Ripple Field exemple 1

① Respirez — hero d'une application de méditation ou de sommeil

QuandÉcran d'accueil sombre d'une application bien-être : titre, durée de la séance, un bouton, le tout sur un dégradé nuit.
PourquoiDes cercles qui s'ouvrent lentement et s'effacent évoquent une goutte sur l'eau et un rythme respiratoire ; les teintes bleu-violet du code s'accordent au thème nocturne sans réglage. Le débit de 8 par seconde reste calme même sous une souris agitée.
RéglagesVerrou monté à 200 ms (5 anneaux/s), durée 1.6s pour un déploiement plus lent, taille finale 220px, alpha de bordure .45 pour rester en retrait du titre.
Sonar — panneau de supervision d'un réseau — Cursor Ripple Field exemple 2

② Sonar — panneau de supervision d'un réseau

QuandCarte de tableau de bord affichant des nœuds (serveurs, sites) sur un fond quadrillé, avec un en-tête d'état.
PourquoiL'anneau qui se propage depuis le pointeur reprend le code visuel du sonar et du ping : il donne au panneau une lecture « le réseau répond » sans animation permanente qui distrairait des chiffres.
RéglagesTeinte fixée sur le vert d'état (hsla(160, 70%, 60%, .7)), durée .8s, taille finale 120px, verrou 150 ms ; cursor: default, le panneau n'étant pas cliquable.
Lecteur ambient — pochette interactive d'un album — Cursor Ripple Field exemple 3

③ Lecteur ambient — pochette interactive d'un album

QuandVisuel plein cadre d'un lecteur audio (titre, artiste) avec la barre de transport en bas.
PourquoiSur une pochette abstraite, les anneaux prolongent la musique en réaction au geste ; le découpage par overflow: hidden les garde dans le visuel et hors des commandes. La palette bleu → violet est celle de l'ambient et de l'électro.
RéglagesPlage de teinte élargie 200 + 100 × Math.random() (cyan → magenta), bordure 3px, taille finale 260px, durée 1.4s.

Comment ça marche

Chaque [data-ripple-field] reçoit un écouteur mousemove gardé par un verrou temporel : un booléen t passe à vrai à la première émission et un setTimeout de 120 ms le relâche. Tout mouvement pendant ce délai est ignoré, quelle que soit la fréquence de la souris : le débit plafonne à 1000 / 120 ≈ 8 anneaux par seconde. Mesuré dans Chromium : 9 anneaux créés pour 1,29 s de mouvement continu, 7 présents au plus au même instant.

Pour chaque émission retenue, le script crée un div.alchemy-cursor-ripple, le place en left/top aux coordonnées du pointeur relatives à la zone (getBoundingClientRect) et lui donne une couleur de bordure hsla(220 + 60 × Math.random(), 70%, 65%, .6) — une teinte entre le bleu (220°) et le violet (280°), à 60 % d'opacité. Le div est ajouté dans la zone et s'auto-détruit : un écouteur animationend appelle remove(). Il n'y a ni boucle requestAnimationFrame ni minuterie de nettoyage : la fin de l'animation CSS est la seule horloge.

Le mouvement est entièrement en CSS : .alchemy-cursor-ripple est position: absolute, border: 2px solid, rond, pointer-events: none, et joue cursorRippleExpand 1s ease-out forwards. Les images clés animent deux grandeurs à la fois : width/height de 0 à 150 px et transform: translate(−50%, −50%) scale(0 → 1). Le diamètre visible est leur produit : mesuré, 1 px à 50 ms, 14 px à 200 ms, 70 px à 500 ms, 150 px à 1 s — l'anneau part très lentement puis s'ouvre vite, alors qu'une seule grandeur en ease-out aurait fait l'inverse. L'opacité descend de 1 à 0 sur la même courbe (0,69 à 200 ms, 0,32 à 500 ms). La translate(−50%, −50%) centre l'anneau sur le point d'émission.

La zone .ripple-field-zone fait 100% × 300px, en dégradé linear-gradient(135deg, #0f0f2e, #1a1a3e), cursor: pointer, position: relative (repère des left/top) et overflow: hidden, qui découpe les anneaux nés près des bords. Le CSS vendu se limite à cette zone, à .alchemy-cursor-ripple et à ses images clés cursorRippleExpand : rien qui vienne d'un autre effet.

Accessibilité

  • prefers-reduced-motion absent du code : les anneaux s'ouvrent quelle que soit la préférence système. Ajoutez @media (prefers-reduced-motion: reduce) { .alchemy-cursor-ripple { animation: none; display: none } } — attention, sans animation l'événement animationend ne vient jamais et les div ne seraient plus retirés, d'où le display: none ; mieux : conditionnez l'écouteur à matchMedia('(prefers-reduced-motion: reduce)').matches pour ne rien créer.
  • L'effet est exclusivement souris : aucun anneau au clavier, et au tactile au plus un par toucher (un seul mousemove émulé). La zone est un div non focalisable ; cursor: pointer promet un clic qui n'existe pas dans le code — remplacez-le par default si la zone n'est pas cliquable, ou branchez une vraie action.
  • Contraste : les anneaux sont des éléments graphiques décoratifs. À 60 % d'opacité sur le dégradé, la teinte la moins visible mesure 2,3:1 et la plus visible 2,9:1, sous le seuil de 3:1 des composants d'interface (WCAG 1.4.11) — acceptable pour un ornement, pas si l'anneau doit signaler quelque chose : passez l'alpha à .9 (≥ 3,5:1). Le texte d'invite fourni, rgba(255,255,255,.15), est à 1,5:1 : remontez-le à .5 (5,3:1) s'il doit être lu.
  • Lecteurs d'écran : les div sont vides, donc muets, mais jusqu'à 8 insertions et retraits par seconde agitent l'arbre d'accessibilité. Posez aria-hidden="true" sur la zone [data-ripple-field]. Le pointeur natif reste affiché (aucun cursor: none) : rien n'est retiré à l'utilisateur.
  • Intégration : la zone doit garder position: relative (sinon les anneaux se placent par rapport à l'ancêtre positionné le plus proche) et une hauteur explicite — le code vendu fixe 300px, une valeur en % dans un parent sans hauteur rend 0 px. Conservez overflow: hidden, qui découpe les anneaux de 150 px nés au bord. Aucune remise à zéro à la sortie : chaque anneau se retire seul au bout d'une seconde.

Compatibilité navigateur

Code ES5 (var, function) : il requiert getBoundingClientRect, setTimeout, l'événement animationend, une animation CSS sur width/height, transform et opacity, et les couleurs hsla(). Zéro dépendance.

Chrome 43+✓ Complet
Firefox 16+✓ Complet
Safari 9+✓ Complet
Edge 12+✓ Complet
Mobile iOS✓ Statique (pas de mousemove)
Android Chrome✓ Statique (pas de mousemove)

Sans JavaScript, la zone garde son dégradé et son texte d'invite : aucun anneau, rien de cassé. Sans animations CSS (navigateurs très anciens), un anneau serait créé à 0 px, invisible, et jamais retiré faute d'animationend — un div vide par émission, sans conséquence visible.

Le code

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

index.html — structure
<div class="ripple-field-zone" data-ripple-field>
  <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 pour créer des ondulations</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
Verrou (JS — setTimeout(..., 120)) 120 ms Intervalle minimal entre deux anneaux, donc débit maximal (1000 / 120 ≈ 8 par seconde). 60 double la densité ; 250 donne des anneaux isolés, lisibles un à un. Le nombre d'anneaux simultanés vaut durée / verrou.
Teinte (JS — 60 × Math.random() + 220) 220° à 280° (bleu → violet) Plage de teinte HSL tirée par anneau ; saturation 70 % et luminosité 65 % sont fixes dans la chaîne hsla(). 0 + 40 × Math.random() = rouges et oranges ; une constante = anneaux unis.
Alpha de la bordure (JS — , 0.6)) .6 Opacité de départ de la couleur de bordure, multipliée ensuite par l'opacity animée. .9 rend l'anneau franc (≥ 3,5:1 sur le fond) ; .35 le fond dans le dégradé.
Taille finale (CSS — @keyframes cursorRippleExpand, 100 %) 150 px Diamètre atteint à la fin. Modifiez width et height ensemble. Comme scale passe aussi de 0 à 1, l'anneau reste petit longtemps : à mi-parcours il ne mesure que 70 px sur 150.
Durée (CSS — animation: cursorRippleExpand 1s) 1s ease-out Vie de l'anneau et vitesse d'ouverture. Le retrait du div suit automatiquement (animationend). .6s = gouttes nerveuses ; 2s = ondes lentes, jusqu'à 16 anneaux présents au verrou par défaut.
Épaisseur (CSS — border: 2px solid) 2 px Trait de l'anneau. Notez que scale(0 → 1) agit sur tout l'élément : la bordure paraît plus fine à la naissance et n'atteint 2 px qu'à la fin. 3px pour un anneau qui reste visible dès le départ.
Départ progressif (CSS — scale(0) à 0 %) scale(0) + width 0 Remplacez scale(0) par scale(1) à 0 % pour n'animer que la largeur : l'anneau s'ouvre alors vite au début et ralentit, courbe ease-out classique, et la bordure garde son épaisseur du premier au dernier instant.

FAQ

Parce que deux grandeurs grandissent en même temps : la boîte (width/height de 0 à 150 px) et son scale (de 0 à 1). Le diamètre affiché est le produit des deux ; en début de courbe, deux petites valeurs multipliées donnent presque rien — mesuré, 1 px à 50 ms et 14 px à 200 ms, puis 70 px à 500 ms et 150 px à 1 s. C'est une propagation qui accélère, à l'inverse d'une goutte réelle. Pour une ouverture rapide qui ralentit, mettez scale(1) dès l'image 0 % et laissez la largeur seule porter l'animation.
Chaque anneau écoute son propre animationend et s'enlève quand l'animation d'une seconde se termine ; il n'y a pas de minuterie ni de boucle. Au débit maximal, 8 anneaux par seconde pendant 1 s = 8 div présents au plus, mesurés 7. La faiblesse de ce choix : si l'animation ne joue pas — animation: none posé par une règle de mouvement réduit, ou un navigateur sans animations CSS — l'événement ne vient jamais et les div s'accumulent, invisibles mais réels. Dans ce cas, ne créez pas d'anneau du tout (condition matchMedia autour de l'écouteur) plutôt que de cacher ceux qui existent.
Plusieurs zones, oui, sans rien changer : le script fait querySelectorAll('[data-ripple-field]') et donne à chacune son propre verrou et ses propres anneaux. Toute la page est possible en posant l'attribut sur un conteneur en position: relative et overflow: hidden qui l'englobe — les left/top des anneaux sont relatifs à ce conteneur, pas à la fenêtre : s'il défile, les anneaux nés en haut restent en haut. Pour des anneaux fixes à l'écran, il faudrait passer .alchemy-cursor-ripple en position: fixed et utiliser clientX/clientY sans soustraire le rectangle de la zone.