Curseurs✨ Premium

Magnetic Cursor

Un bouton qui se penche vers ton curseur tant que tu le survoles : le JS mesure l'écart au centre, l'écrit dans deux variables CSS, et le :hover décide quand la translation s'applique.

JSCSS Variables

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

CTA principal d'un hero — SaaS ou landing produit — Magnetic Cursor exemple 1

① CTA principal d'un hero — SaaS ou landing produit

QuandLe bouton d'action principal (« Essai gratuit », « Réserver une démo ») trône seul dans le hero et mérite une micro-interaction qui le distingue des boutons secondaires.
PourquoiL'inclinaison ne se déclenche qu'au survol du bouton lui-même : elle récompense une intention déjà formée au lieu de la solliciter de loin. Le bouton semble venir à la rencontre du clic — une réactivité qui crédibilise le produit sans rien ajouter à la page.
RéglagesCoefficient abaissé à 0,2 pour un bouton large (l'écart mesuré grandit avec la taille de l'élément) ; transition à 0,25 s pour une réponse nette.
Icônes sociales d'un footer ou d'une page contact — Magnetic Cursor exemple 2

② Icônes sociales d'un footer ou d'une page contact

QuandUne rangée de petites icônes (GitHub, LinkedIn, X…) de 40 à 48 px, cibles de clic notoirement étroites, qu'on veut rendre plus vivantes et plus faciles à viser.
PourquoiSur un petit élément, l'inclinaison agit comme une assistance à la visée : l'icône se décale dans la direction du curseur et reste sous lui pendant l'approche du clic. L'effet reste discret, l'écart au centre ne dépassant jamais la moitié de l'icône.
RéglagesCoefficient monté à 0,4 — sur 44 px, le débattement reste sous 9 px ; transition raccourcie à 0,2 s ; enveloppe .magnetic-area dupliquée autour de chaque icône avec la variante querySelectorAll décrite en FAQ.
Vignettes d'un portfolio ou d'une grille de projets — Magnetic Cursor exemple 3

③ Vignettes d'un portfolio ou d'une grille de projets

QuandUn studio ou un freelance présente ses projets en grille de cartes cliquables et cherche une signature d'interaction qui ne repose ni sur le zoom ni sur l'ombre portée.
PourquoiUne carte qui se penche de quelques pixels vers le curseur paraît montée sur rotule. La translation suivant la position du pointeur dans la carte, chaque zone de survol produit une posture différente — physique, pas scriptée.
RéglagesCoefficient réduit à 0,08–0,12 : sur une carte de 300 px, l'écart au centre atteint 150 px et 0,3 produirait 45 px de déplacement — beaucoup trop ; transition allongée à 0,4 s pour donner du poids ; :hover conservé comme unique déclencheur.

Comment ça marche

L'effet repose sur une séparation stricte des rôles. Le bouton est enveloppé dans un conteneur .magnetic-area, et c'est ce conteneur — pas le bouton — qui bouge. Côté CSS, la règle clé tient en une ligne : .magnetic-area:hover reçoit transform: translate(var(--magnetic-x, 0), var(--magnetic-y, 0)). La translation est pilotée par deux variables CSS avec un repli à 0, et elle n'est déclarée que sous :hover : hors survol, aucune transformation n'existe. Une transition: transform .3s sur le conteneur lisse à la fois la poursuite et le retour au repos.

Côté JavaScript, l'écouteur mousemove est posé sur le conteneur lui-même, pas sur la zone qui l'entoure. À chaque mouvement, getBoundingClientRect() donne la position réelle de l'élément ; le script en déduit l'écart du pointeur au centre (clientX - rect.left - rect.width / 2, idem en Y), le multiplie par 0,3 et écrit le résultat dans --magnetic-x et --magnetic-y via style.setProperty(). Le JS ne touche jamais à transform : il fournit des nombres, le CSS décide de leur application. Un second écouteur, mouseleave, remet les deux variables à 0px. Le tout tient en une quinzaine de lignes, sans requestAnimationFrame ni bibliothèque.

Cette architecture produit un comportement émergent : comme getBoundingClientRect() mesure la position déjà transformée, chaque mousemove calcule l'écart depuis l'endroit où l'élément se trouve à cet instant. La traction s'auto-amortit : l'élément converge sous le pointeur au lieu d'osciller, et se stabilise vers 23 % de l'écart initial (0,3 / 1,3), et non les 30 % du coefficient. Autre conséquence : sans JavaScript, les variables ne sont jamais écrites, le repli 0 s'applique et le bouton reste pleinement fonctionnel — l'effet est une amélioration progressive, pas une dépendance.

Accessibilité

  • prefers-reduced-motion : absent du code. Ajoutez @media (prefers-reduced-motion: reduce) { .magnetic-area:hover { transform: none; } } — une seule règle neutralise l'effet, puisque toute la translation passe par ce sélecteur.
  • L'effet est 100 % souris : mousemove ne se déclenche ni au clavier ni au tactile. Le <button> natif reste focusable et activable à Entrée/Espace, mais aucun style :focus-visible n'est défini — ajoutez-en un, les utilisateurs clavier ne verront jamais l'inclinaison magnétique.
  • Sur pointeur grossier (tactile), aucun mousemove ne circule en usage normal : l'effet est inerte, le bouton se comporte normalement. Pour bloquer les mousemove émulés au tap par certains navigateurs, conditionnez l'écouteur à matchMedia('(hover: hover) and (pointer: fine)').
  • Pour les lecteurs d'écran, rien ne change : le déplacement est purement visuel, le <button> conserve son nom accessible et sa place dans l'arbre d'accessibilité. Ne portez aucune information par le mouvement lui-même — il est invisible pour une partie des utilisateurs.

Compatibilité navigateur

Repose sur les variables CSS (var() écrites via setProperty()), les transitions et getBoundingClientRect(). Le facteur limitant est le support des custom properties — acquis partout aujourd'hui, absent d'IE 11.

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

La dégradation est naturelle : sans variables CSS (IE 11), <code>translate(var(…))</code> est invalide, le navigateur ignore la déclaration et le bouton s'affiche statique mais pleinement cliquable. Sans JavaScript, les variables ne sont jamais écrites et le repli 0 de <code>var(--magnetic-x, 0)</code> garantit une translation nulle. Aucun code de secours à écrire.

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
Coefficient d'attraction (x * 0.3 / y * 0.3 dans le JS) 0.3 Les deux multiplications fixent l'amplitude : la valeur écrite dans les variables vaut 30 % de l'écart au centre. 0,1 = frémissement à peine perceptible ; 0,5 = poursuite marquée. Modifiez les deux occurrences ensemble pour garder un mouvement isotrope.
transition: transform .3s (sur .magnetic-area) .3s Lisse chaque nouvelle valeur des variables et anime le retour au repos après mouseleave. 0,15 s = réponse vive, collée au curseur ; 0,5 s = traîne visible, le bouton semble lourd. C'est l'unique réglage de vitesse de l'effet.
Courbe du retour (timing-function) ease (implicite) Le raccourci laisse la courbe ease par défaut. Passez à cubic-bezier(0.34, 1.56, 0.64, 1) pour un retour élastique qui dépasse légèrement la position de repos — la sensation « lâcher d'aimant » typique de ce genre d'interaction.
Le garde :hover (.magnetic-area:hover) translation appliquée uniquement au survol Déclarer le transform sous :hover est une sécurité : hors survol, aucune translation ne peut subsister. Déplacez la déclaration sur .magnetic-area seul si vous pilotez les variables autrement — le retour au repos ne dépendra alors que du mouseleave JS.
Coefficients dissymétriques X / Y 0.3 sur les deux axes Rien n'impose le même facteur sur les deux axes : y * 0.15 avec x * 0.3 contraint la verticale et produit un glissement latéral — pertinent pour un bouton large et bas, où l'écart horizontal domine largement l'écart vertical.
Le contenu de l'enveloppe (.magnetic-btn) un <button> stylé en dégradé .magnetic-area porte l'écouteur, la transition et la translation — le bouton n'est qu'un passager. Remplacez-le par un lien, une icône SVG ou une carte entière : tant que l'élément reste dans l'enveloppe, la mécanique fonctionne sans toucher au JavaScript.

FAQ

« Magnetic Effect » crée un champ d'attraction : il écoute la zone entière, calcule la distance euclidienne au centre de la cible et applique une force dégressive dans un rayon de 150 px — l'élément vient au curseur avant même d'être survolé. « Magnetic Cursor » fait l'inverse : l'écouteur est sur l'élément lui-même, sans rayon ni calcul de distance, et l'inclinaison n'existe que pendant le survol, verrouillée par :hover. L'un attire de loin, l'autre accompagne au contact. Et côté code : le premier écrit le transform inline en JS ; ici le JS n'écrit que deux variables, le CSS garde la main.
Le code cible un id unique via getElementById — une seule instance. Pour en équiper plusieurs, passez par la classe : parcourez document.querySelectorAll('.magnetic-area') et attachez la paire mousemove/mouseleave à chaque élément de la boucle. Les variables étant écrites sur le style inline de chaque enveloppe, les instances sont indépendantes.
Rien — et c'est cohérent avec la mécanique : sans souris, aucun mousemove ne circule, les variables restent au repli 0 et le bouton se comporte normalement. Inutile de désactiver quoi que ce soit. Pour distinguer stylet ou trackpad, la media query (hover: hover) and (pointer: fine) reste le bon critère.