Magnetic Button
Le bouton glisse vers le curseur (30 % de l'écart au centre) et revient en 300 ms — un CTA qui répond à la main avant le clic.
Mis à jour le
Cet effet fait partie d'Effect.Labs — 840 effets vanilla, une partie gratuite, une partie premium. Boutons comprend 27 effets, dont 2 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
Le script pose un écouteur mousemove sur chaque élément portant l'attribut data-magnetic. À chaque événement, getBoundingClientRect() donne la position et la taille du bouton, puis deux écarts sont calculés : a = clientX − left − width/2 et l = clientY − top − height/2 — la position du curseur par rapport au centre, en pixels.
Le déplacement est écrit en style inline : transform: translate(0.3·a px, 0.3·l px). Le bouton parcourt donc 30 % du chemin vers le curseur (un peu moins en pratique : le rectangle est relu sur le bouton déjà déplacé, l'écart mesuré est donc plus court). Le débattement maximal vaut 0,3 × demi-taille : ±30 px en largeur et ±8 px en hauteur pour un bouton de 200 × 52 px.
Le lissage est entièrement en CSS : transition: transform .3s sur .btn-magnetic (courbe ease par défaut). Chaque nouvelle cible est atteinte en 300 ms alors que le curseur bouge plus vite — d'où l'impression que le bouton est tiré, en retard sur la main. Au mouseleave, le script écrit translate(0, 0) et la même transition ramène le bouton à sa place.
La sélection par attribut (querySelectorAll('[data-magnetic]')) rend l'effet multi-instance sans identifiant : tous les boutons marqués sont pris en une passe. L'écouteur est posé sur le bouton lui-même, pas sur une zone autour : l'attraction ne commence qu'au contact du curseur avec le bouton (voir la FAQ pour une zone d'attraction élargie).
Accessibilité
- prefers-reduced-motion absent du code : le bouton se déplace à chaque survol quelle que soit la préférence système. Ajoutez
@media (prefers-reduced-motion: reduce) { .btn-magnetic { transition: none; transform: none !important } }— le!importantest nécessaire parce que letransformest écrit en inline par le JS — ou testezmatchMedia('(prefers-reduced-motion: reduce)').matchesavant d'attacher les écouteurs. - L'effet est exclusivement souris (
mousemove/mouseleave). Au clavier et au toucher, le bouton est un<button>natif ordinaire : rien n'est perdu, le déplacement n'est qu'un ornement. - Contraste insuffisant dans la version fournie : texte blanc sur le dégradé cyan → bleu, soit 2,4:1 côté
#06b6d4et 3,7:1 côté#3b82f6, sous le seuil AA de 4,5:1 pour un texte de 16 px. Assombrissez le dégradé (#0e7490 → #1d4ed8donne 5,4:1 et 6,7:1) ou passez le libellé en 18 px gras (seuil 3:1). - Le code écrit
border: nonemais ne touche pas àoutline: l'anneau de focus natif reste visible à la tabulation. Ne l'enlevez pas ; si vous le restylez, utilisez:focus-visibleavec un contour de 2 px contrasté. - Cible tactile ≈ 52 px de haut (padding 16 px + texte 16 px), au-dessus du minimum de 44 px. Lecteurs d'écran :
<button>natif avec un libellé texte, aucun ARIA à ajouter ; letransforminline ne modifie pas l'arbre d'accessibilité.
Compatibilité navigateur
Requiert getBoundingClientRect, les fonctions fléchées et gabarits de chaîne ES2015, et la transition CSS de transform — tous les navigateurs depuis 2016. Zéro dépendance.
Sans JavaScript, le bouton s'affiche et fonctionne normalement, dégradé et coins arrondis compris ; seul le déplacement disparaît. Aucune erreur si la page ne contient aucun élément data-magnetic : la boucle forEach est simplement vide.
Le code
Structure HTML à coller dans votre page (le CSS + JS sont inclus avec Premium) :
<button class="demo-btn btn-magnetic" data-magnetic>Magnetic</button>
Le code complet HTML + CSS + JS de Magnetic Button, prêt à coller — et les 840 effets. Ou 299 € TTC une fois, accès à vie.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Coefficient .3 (JS — translate(${.3*a}px, ${.3*l}px)) |
.3 | Fraction de l'écart curseur/centre reportée sur le bouton. 0,15 = frémissement ; 0,5 = le bouton parcourt la moitié du chemin et sort visiblement de sa place. |
| transition: transform .3s (CSS — .btn-magnetic) | .3s ease | Latence du suivi. .1s colle au curseur (rendu nerveux) ; .5s donne un mouvement lourd. Le retour au mouseleave utilise la même durée. |
Sélecteur [data-magnetic] (JS) |
attribut sans valeur | Posez l'attribut sur n'importe quel élément (lien, carte, icône) pour l'aimanter — le script ne suppose rien de la balise. Retirez-le pour désactiver un bouton sans toucher au JS. |
| background (CSS — .btn-magnetic) | linear-gradient(135deg, #06b6d4, #3b82f6) | Dégradé cyan → bleu. Remplacez par vos couleurs ; pour un contraste AA avec le texte blanc, restez sur des teintes foncées (#0e7490 → #1d4ed8). |
| padding / font-size (CSS — .demo-btn) | 16px 32px / 1rem | Taille du bouton — donc débattement maximal, calculé sur width/2 et height/2. Un bouton deux fois plus large bouge deux fois plus. |
| border-radius (CSS — .demo-btn) | 12px | Forme des coins. 999px donne une pilule ; le mouvement ne dépend pas de la forme. |
Retour au repos (JS — mouseleave) |
translate(0, 0) | Écrivez translate(0, 0) scale(1) ici et ajoutez scale(1.05) à la chaîne du mousemove pour combiner attraction et légère loupe. |
FAQ
mousemove est écouté sur le bouton lui-même, l'effet commence au contact. Pour une zone d'attraction, enveloppez le bouton dans un <div> plus grand (par exemple padding: 40px), posez data-magnetic sur ce parent, et appliquez le transform à l'enfant : remplacez e.style.transform par e.firstElementChild.style.transform dans les deux écouteurs.addEventListener dans un useEffect / onMounted avec une référence sur le bouton, et retirez-les au démontage (removeEventListener). La logique de calcul reste identique.transform ne participe pas au flux, les voisins ne bougent pas et aucun reflow n'est provoqué chez eux. Prévoyez simplement une marge au moins égale au débattement maximal (0,3 × demi-largeur) pour que le bouton ne chevauche pas un voisin, et évitez un parent en overflow: hidden qui le couperait.