Boutons✨ Premium

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.

JSMouseInteractive

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

Bouton d'essai gratuit — carte de tarification SaaS — Magnetic Button exemple 1

① Bouton d'essai gratuit — carte de tarification SaaS

QuandL'utilisateur compare les formules d'une page Tarifs ; chaque carte se termine par un bouton d'engagement.
PourquoiLe déplacement isole le CTA du reste de la carte (prix, liste de fonctionnalités) sans couleur supplémentaire : il signale « c'est ici qu'on clique » dès que la main s'approche.
RéglagesCoefficient 0,3 → 0,2 pour une grille dense (les boutons voisins ne se chevauchent pas), transition: transform .25s, bouton en width: 100%.
CTA principal — hero de landing page — Magnetic Button exemple 2

② CTA principal — hero de landing page

QuandUn seul bouton primaire au centre du hero, accompagné d'un lien secondaire immobile.
PourquoiSur un grand bouton, le débattement atteint ±35 px et devient la première interaction visible de la page ; le lien secondaire, qui ne bouge pas, sert de repère.
RéglagesCoefficient 0,3 conservé, transition: transform .35s pour un suivi plus lourd, padding: 18px 40px, texte 17 px / 700.
Bandeau de contact — site d'agence ou de freelance — Magnetic Button exemple 3

③ Bandeau de contact — site d'agence ou de freelance

QuandEn bas de page, une bande « Un projet ? Parlons-en » avec un unique bouton.
PourquoiSur un fond uni sans autre élément interactif, l'attraction se perçoit nettement ; c'est le motif classique des sites d'agence, et le bouton en pilule s'y prête.
RéglagesCoefficient 0,4 (débattement plus franc, ±40 px sur un bouton de 200 px), transition: transform .2s, border-radius: 999px.

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 !important est nécessaire parce que le transform est écrit en inline par le JS — ou testez matchMedia('(prefers-reduced-motion: reduce)').matches avant 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é #06b6d4 et 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 → #1d4ed8 donne 5,4:1 et 6,7:1) ou passez le libellé en 18 px gras (seuil 3:1).
  • Le code écrit border: none mais ne touche pas à outline : l'anneau de focus natif reste visible à la tabulation. Ne l'enlevez pas ; si vous le restylez, utilisez :focus-visible avec 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 ; le transform inline 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.

Chrome 49+✓ Complet
Firefox 45+✓ Complet
Safari 10+✓ Complet
Edge 79+✓ Complet
Mobile iOS✓ Statique (pas de survol)
Android Chrome✓ Statique (pas de survol)

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) :

index.html — structure
<button class="demo-btn btn-magnetic" data-magnetic>Magnetic</button>
Débloquer le code complet — 9,90 € TTC/mois

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éfautEffet
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

Pas avec le code fourni : le 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.
Le script s'exécute une fois au chargement : les boutons montés ensuite ne sont pas écoutés. Déplacez les deux 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.
Non : 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.