Curseurs✨ Premium

Elastic Cursor

Un disque de 24 px collé au pointeur, étiré par un ressort amorti selon la vitesse — jusqu'à scaleX 2,2, orienté par atan2 — et une traîne de 16 px qui le suit à 15 % par image ; à l'arrêt, le disque passe par un creux à 0,91 et se stabilise en 130 ms. La zone masque le pointeur natif (cursor: none).

JSTransformPhysics

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

Galerie de projets — vignettes survolées par un disque — Elastic Cursor exemple 1

① Galerie de projets — vignettes survolées par un disque

QuandGrille de six vignettes d'un studio, chacune avec un titre, sur un fond sombre qui occupe toute la zone.
PourquoiLe disque qui s'étire d'une vignette à l'autre donne une signature au portfolio sans toucher aux images ; la traîne à 15 % dessine la trajectoire du regard. Les vignettes restent cliquables, les disques étant en pointer-events: none.
RéglagesDisque en dégradé blanc → gris clair et traîne blanche à .35 — les vignettes sont colorées, le violet du code se confondrait avec l'indigo de la première —, plafond d'étirement .8 (min(.04 × v, .8)) pour une goutte plus discrète sur les visuels.
Laboratoire d'interactions — hero d'une agence — Elastic Cursor exemple 2

② Laboratoire d'interactions — hero d'une agence

QuandHero d'une agence ou d'un studio créatif : titre, sous-titre et bouton sur un dégradé nuit, la zone couvrant tout l'écran d'accueil.
PourquoiRemplacer la flèche par un disque vivant dès l'arrivée annonce le savoir-faire sans un mot ; le rebond à l'arrêt (creux à 0,91 puis retour en 130 ms) se remarque à chaque pause de la main. Le texte et le bouton restent lisibles et cliquables sous le disque.
RéglagesRéglages du code conservés (.15, .04, plafond 1.2), cursor: none retiré au-dessus de la navigation pour rendre la flèche sur les liens du menu.
Table de air hockey — palet piloté à la souris — Elastic Cursor exemple 3

③ Table de air hockey — palet piloté à la souris

QuandMini-jeu ou écran d'attente : un terrain avec ligne médiane, cercle central, deux buts et un score.
PourquoiLe disque qui s'allonge dans le sens de la frappe et rebondit à l'arrêt est déjà un palet ; la traîne translucide fait office d'effet de vitesse. Le terrain fournit des repères visuels qui rendent l'étirement lisible.
RéglagesDisque 32 px en blanc cassé, traîne 24 px, raideur du ressort .3 et amortissement .5 pour un rebond plus marqué (creux plus profond, deux oscillations).

Comment ça marche

La zone .elastic-zone (100% × 300px, dégradé radial #1a1a3e → #0f0f25, position: relative, cursor: none) contient deux enfants absolus, ronds, en pointer-events: none et transform: translate(−50%, −50%) : .elastic-cursor-el, le disque de 24 px en dégradé #a78bfa → #6366f1 (z-index: 2), et .elastic-cursor-trail, la traîne de 16 px en rgba(139,92,246,.3) (z-index: 1). Trois écouteurs sur la zone : mouseenter lève un drapeau et lance la boucle requestAnimationFrame ; mouseleave le baisse — la boucle s'arrête à l'image suivante — et met les deux éléments à opacity: 0 ; mousemove enregistre la position du pointeur relative à la zone et remet opacity: 1.

À chaque image, la boucle fait quatre choses. La traîne rattrape le pointeur par interpolation : l += .15 × (r − l), soit 15 % de l'écart restant par image — 95 % du chemin en une vingtaine d'images, environ 0,3 s (mesuré : recollée à moins de 1 px en 284 ms). Le disque, lui, est placé exactement sous le pointeur. La vitesse est le déplacement du pointeur depuis l'image précédente, en px par image ; elle fixe une cible d'étirement g = 1 + min(.04 × vitesse, 1.2) : 1 à l'arrêt, 2,2 dès 30 px par image. L'angle q = atan2(dy, dx) n'est mis à jour que si la vitesse dépasse 0,5 px par image : à l'arrêt, le disque garde sa dernière direction, et c'est le long de cet axe qu'il rebondit.

Le ressort — ajouté le 5 septembre 2026 pour tenir la promesse du nom — relie l'étirement courant v à sa cible : w += (g − v) × .2 (raideur), w *= .6 (amortissement), v += w. Le disque reçoit rotate(q) scaleX(v) scaleY(1 / (.5 + .5 × v)) : à v = 2,2, la hauteur tombe à 0,625, le disque devient une goutte allongée dans le sens du mouvement. Mesuré dans Chromium sur un balayage horizontal rapide : scaleX 2,28 au plus fort (le ressort dépasse la cible), scaleY 0,61, rotation 0° ; balayage vertical, rotation 90°.

À l'arrêt, la cible retombe à 1 et le ressort la traverse : mesuré, scaleX 2,08 → 1,62 → 1,22 → 1,00 à 58 ms, creux à 0,91 à 83 ms, 0,95 → 0,98 → 1,00 à 133 ms, puis stable à ±0,02 — le rebond promis, en un peu plus d'un dixième de seconde. Les deux disques sont à opacity: 0 dans le CSS livré : avant le premier survol, rien n'est visible — sans cet état initial, ils apparaissaient dans le coin haut-gauche de la zone, à −4/−4 px et 0/0 px, opacité 1 (mesuré). Une limite du code : bien que will-change: transform soit déclaré, left/top sont réécrits à chaque image, ce qui déclenche une mise en page — la seule transform aurait suffi.

Accessibilité

  • prefers-reduced-motion absent du code : étirement, rebond et traîne jouent quelle que soit la préférence. Ne posez pas les écouteurs si matchMedia('(prefers-reduced-motion: reduce)').matches — et, dans ce cas, retirez aussi cursor: none de la zone, sinon l'utilisateur perd son pointeur sans rien à la place.
  • cursor: none supprime le pointeur natif dans toute la zone : le disque est l'unique repère de position, et il n'existe que pendant le survol, après le premier mousemove. Sans JavaScript, ou avant son exécution, la zone est un trou sans pointeur — n'appliquez cursor: none que sous une classe posée par le script (.js .elastic-zone). Les éléments interactifs placés dans la zone restent cliquables (les disques sont en pointer-events: none) mais sont visés avec un disque de 24 px, pas une flèche.
  • Contraste : le disque en #a78bfa → #6366f1 mesure 6,1:1 et 4,2:1 sur le dégradé de la zone, au-dessus du seuil de 3:1 d'un indicateur graphique — il reste repérable. La traîne rgba(139,92,246,.3) est à 1,5:1, purement décorative. Le texte d'invite fourni, rgba(255,255,255,.15), est à 1,6:1 : remontez-le à .5 (5:1) s'il doit être lu.
  • Tactile : aucun mousemove, donc pas de disque, et cursor: none n'y change rien. Mais certains navigateurs mobiles émettent un mouseenter au toucher sans jamais émettre de mouseleave : la boucle requestAnimationFrame démarre et ne s'arrête plus. Conditionnez l'initialisation à matchMedia('(hover: hover) and (pointer: fine)'). Lecteurs d'écran : deux div vides, muets ; posez aria-hidden="true" sur la zone.
  • Intégration : la zone doit rester en position: relative avec une hauteur explicite (300px dans le code vendu ; un pourcentage sans parent dimensionné rend 0 px), et contenir directement les deux div — le script les cherche par querySelector dans la zone et ne fait rien s'il en manque un. La remise à zéro à la sortie est faite (opacité 0, boucle arrêtée, pointeur natif rendu hors de la zone) ; l'état initial est couvert par l'opacity: 0 des deux éléments dans le CSS.

Compatibilité navigateur

Code ES5 (var, function) : il requiert requestAnimationFrame, getBoundingClientRect, les transformations 2D et linear-gradient. cursor: none est reconnu partout depuis 2012. Zéro dépendance.

Chrome 26+✓ Complet
Firefox 16+✓ Complet
Safari 9+✓ Complet
Edge 12+✓ Complet
Mobile iOS✓ Inerte (pas de pointeur)
Android Chrome✓ Inerte (pas de pointeur)

Sans JavaScript, la zone masque le pointeur natif (cursor: none) et les deux disques restent invisibles (opacity: 0) : l'utilisateur n'a plus de curseur dans la zone. Réservez cursor: none à une classe ajoutée par le script. Sans requestAnimationFrame (IE 9), la boucle ne démarre pas et le même trou apparaît.

Le code

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

index.html — structure
<div class="elastic-zone" data-elastic-cursor>
  <div class="elastic-cursor-el"></div>
  <div class="elastic-cursor-trail"></div>
  <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 voir l'élasticité</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
Suivi de la traîne (JS — l += .15 × (r − l)) .15 par image Fraction de l'écart rattrapée à chaque image. .05 = traîne lointaine et lente (95 % du chemin en 1 s) ; .4 = presque collée au disque. Vaut pour x et y.
Sensibilité à la vitesse (JS — .04 × vitesse, plafond 1.2) +0,04 par px/image, max +1,2 (scaleX 2,2) Le coefficient fixe à quelle vitesse la goutte s'allonge (.08 : deux fois plus tôt), le plafond l'étirement maximal. min(.02 × v, .6) donne un disque qui se déforme à peine.
Ressort (JS — w += (g − v) × .2 ; w *= .6) raideur .2, amortissement .6 Raideur = vitesse de convergence vers la cible ; amortissement = fraction de la vitesse conservée à chaque image. .3 / .5 : rebond plus profond et plus court ; .1 / .8 : oscillations lentes et visibles ; .2 / 0 : plus de rebond du tout.
Seuil de rotation (JS — t > .5) .5 px/image En dessous, l'angle n'est pas mis à jour : le disque garde l'axe de son dernier mouvement et rebondit le long de cet axe. Montez à 2 pour ignorer les micro-tremblements de la main.
Tailles (CSS — .elastic-cursor-el 24 px, .elastic-cursor-trail 16 px) 24 px / 16 px Diamètres au repos ; l'étirement les multiplie (24 px devient 53 × 15 px à scaleX 2,2). Le translate(−50%, −50%) garde le centre sous le pointeur quelle que soit la taille.
Couleurs (CSS — dégradé #a78bfa → #6366f1, traîne rgba(139,92,246,.3)) violet → indigo, traîne violette à 30 % Le disque doit rester à 3:1 au moins sur votre fond, c'est le seul pointeur. Une traîne plus opaque (.6, 2,3:1) devient un second point visible plutôt qu'une ombre.
cursor: none (CSS — .elastic-zone) pointeur natif masqué Indispensable pour que le disque remplace la flèche, dangereux sans JavaScript. Déplacez-le sous une classe posée par le script (.js .elastic-zone { cursor: none }) et n'en héritez pas sur les liens si vous voulez garder la flèche sur eux.

FAQ

Rien : les règles .elastic-cursor-el et .elastic-cursor-trail posent opacity: 0. Positionnés en absolu sans left/top, les deux div se trouvent bien à l'origine de la zone, décalés d'un demi-diamètre vers l'extérieur par le translate(−50%, −50%) (le disque à −4/−4 px, la traîne à 0/0 px), mais invisibles. Le script écrit opacity = '1' en ligne au premier mousemove — l'écriture en ligne l'emporte sur la règle — et '0' au mouseleave. Si vous retirez ces deux déclarations, les disques apparaissent dans le coin dès le chargement, à opacité 1 (mesuré).
Par un ressort amorti sur l'étirement, ajouté au code le 5 septembre 2026. À chaque image, l'étirement v est tiré vers sa cible g (1 à l'arrêt, jusqu'à 2,2 en vitesse) : w += (g − v) × .2 ajoute à la « vitesse d'étirement » un cinquième de l'écart, w *= .6 en dissipe 40 %, v += w applique le tout. Quand la main s'arrête, v descend de 2,2 vers 1, dépasse (mesuré : creux à 0,91 à 83 ms) et revient (1,00 à 133 ms, stable ensuite). La raideur .2 règle la vitesse du retour, l'amortissement .6 la profondeur du creux : .3 / .5 pour un rebond plus sec, .1 / .8 pour des oscillations lentes, amortissement 0 pour supprimer le rebond.
Dans trois cas, tous liés au cursor: none posé en CSS sur la zone. Sans JavaScript : la règle s'applique, aucun disque ne suit. Avant le premier mousemove : la flèche est déjà masquée, le disque encore invisible. En mouvement réduit, si vous coupez l'effet côté JS sans toucher au CSS. La parade est la même : ne déclarer cursor: none que sous une classe que le script ajoute au html (.js .elastic-zone), après avoir vérifié matchMedia('(hover: hover) and (pointer: fine)') et l'absence de préférence de mouvement réduit. Hors de la zone, le pointeur natif revient toujours — la règle ne s'applique qu'à elle.