Curseurs✨ Premium

Magnetic Elements

Cinq tuiles de 60 px attirées vers le pointeur dans un rayon de 120 px : jusqu'à 25 px de translation, un agrandissement jusqu'à ×1,15 et un halo indigo, recalculés à chaque mousemove et lissés par une transition transform de 300 ms. Aucun curseur personnalisé : ce sont les éléments qui bougent, la flèche système reste.

JSMouseTransform

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

Dock d'applications — icônes qui viennent sous la main — Magnetic Elements exemple 1

① Dock d'applications — icônes qui viennent sous la main

QuandBarre de lancement d'un espace de travail web : cinq applications, un fond de barre translucide.
PourquoiC'est l'usage canonique de l'attraction : les icônes s'avancent vers le pointeur comme celles d'un dock système, et l'agrandissement à ×1,15 confirme la cible avant le clic.
RéglagesDégradés en nuances 600/700 (contraste ≥ 5:1), tuiles en button avec aria-label, écart 28px pour que deux voisines ne se touchent pas en se déplaçant.
Barre de partage — cinq réseaux en pastilles — Magnetic Elements exemple 2

② Barre de partage — cinq réseaux en pastilles

QuandFin d'article ou pied d'une fiche : partager sur cinq réseaux, chacun dans sa pastille ronde.
PourquoiL'attraction met en avant le bouton que l'on va toucher sans agrandir toute la rangée ; le rayon de 120 px fait bouger deux ou trois pastilles à la fois, ce qui suffit à signaler l'interactivité.
RéglagesPastilles 52 px en border-radius: 50%, force maximale 18, rayon 100, écart 32px, halo repris de la couleur de chaque réseau via une variable --glow.
Barre de réactions — émojis attirés par le pointeur — Magnetic Elements exemple 3

③ Barre de réactions — émojis attirés par le pointeur

QuandSous un message ou un commentaire : cinq réactions rapides, fond neutre, une seule ligne.
PourquoiLe mouvement magnétique rend la rangée vivante sans animation permanente ; les tuiles neutres laissent l'émoji porter la couleur, et le léger agrandissement remplace l'infobulle.
RéglagesTuiles #1e1e2a à bordure fine, glyphes émoji 24 px, échelle portée à .25, box-shadow ajouté à la transition pour supprimer le saut du halo.

Comment ça marche

Le conteneur [data-magnetic-zone] (.magnetic-zone, flex centré, gap: 20px, padding: 20px, flex-wrap) contient cinq [data-magnetic-el] (.magnetic-item) : 60 × 60 px, border-radius: 16px, glyphe à 1.5rem, transition: transform .3s cubic-bezier(.23, 1, .32, 1), will-change: transform, cursor: pointer, et un dégradé différent par :nth-child (indigo, rose, cyan, ambre, vert). Le JS itère document.querySelectorAll('[data-magnetic-zone]') : plusieurs zones dans une page fonctionnent sans modification.

À chaque mousemove sur la zone, le script calcule la position locale du pointeur puis, pour chaque tuile, la distance d entre ce point et le centre de la tuile au repos — mesuré une seule fois puis mémorisé (voir plus bas). Si d < 120 : force = 25 × (1 − d / 120), angle atan2, puis transform: translate(cos θ × force, sin θ × force) scale(1 + .15 × (1 − d / 120)) et box-shadow: 0 0 (20 + force)px rgba(99,102,241, .3 + .4 × (1 − d / 120)) — tout en style inline. Sinon : translate(0, 0) scale(1) et box-shadow: none. mouseleave remet les cinq à zéro. Mesuré : à 119 px, translation 0,2 px, échelle 1,001, halo 20 px à 30 % ; à 60 px, 12,5 px, ×1,075, halo 32,5 px à 50 %.

Le halo n'est pas dans la liste des transitions (seule transform y est) : au passage de la frontière des 120 px, box-shadow saute de none à un flou de 20 px à 30 % d'un coup — un « pop » visible, pendant que la translation démarre en douceur. Et il est indigo pour les cinq tuiles, y compris la rose, l'ambre et la verte : la couleur du halo est codée en dur, indépendante du dégradé de la tuile.

Le centre de chaque tuile est relevé une seule fois, au repos : au premier mousemove, le script neutralise le transform de la tuile, lit sa boîte (getBoundingClientRect), mémorise dans t.__repos ses décalages par rapport à la zone (dx, dy) et sa taille, puis rétablit le transform. Aux événements suivants, le centre se déduit de la boîte courante de la zone et de ces décalages : la boîte déjà translatée n'est jamais relue, le vecteur ne s'inverse pas quand la tuile dépasse le pointeur, et la tuile se déplace d'une quantité qui ne dépend que de sa distance de repos, sans osciller. Les décalages étant relatifs à la zone, le défilement de la page ne fait pas dériver le centre. Ce que la mémorisation ne couvre pas : une disposition qui change après la première mesure (fenêtre redimensionnée, tuile ajoutée) — t.__repos garde les anciens décalages. Autre point : .magnetic-zone { height: 100% } dans un parent sans hauteur définie vaut auto — la zone active mesurée fait 100 px de haut (60 + 2 × 20) dans un aperçu de 300 px, et un pointeur 30 px au-dessus de la bande n'attire rien.

Accessibilité

  • Contraste des glyphes : blanc à 24 px normal (grand texte, seuil 3:1) sur cyan #06b6d4 = 2,4:1, ambre #f59e0b = 2,2:1, émeraude #10b981 = 2,5:1, turquoise #14b8a6 = 2,5:1 — quatre des dix arrêts de dégradé échouent ; rose 3,5/3,7 et indigo 4,5/4,2 passent. Utilisez les nuances 600/700 : #0e7490 → #1d4ed8 (5,4 et 6,7:1), #b45309 → #b91c1c (5,0 et 6,5:1), #047857 → #0f766e (5,5:1). Les glyphes décoratifs échappent à la règle : marquez-les aria-hidden.
  • cursor: pointer est posé sur des div qui ne réagissent à aucun clic et n'ont ni rôle ni nom : la main promet une action qui n'existe pas. Soit les tuiles deviennent des button/a (l'effet marche à l'identique, [data-magnetic-el] est un simple sélecteur), soit retirez cursor: pointer.
  • Clavier : des div ne reçoivent pas le focus. Si vous en faites des boutons, l'attraction ne se déclenchera jamais au focus (le code n'écoute que mousemove) : donnez-leur un :focus-visible explicite (outline: 2px solid #c7d2fe; outline-offset: 4px), indépendant de l'effet. La zone cliquable suit la translation : une tuile déplacée de 25 px se clique à sa position visible.
  • prefers-reduced-motion absent : les tuiles glissent et grossissent sous le pointeur. Sous @media (prefers-reduced-motion: reduce), .magnetic-item { transition: none } ne suffit pas — conditionnez plutôt l'écouteur : if (matchMedia('(prefers-reduced-motion: reduce)').matches) return; avant d'attacher mousemove.
  • Lecteurs d'écran : les symboles ♦ ★ ● ▲ ❖ sont annoncés (« losange noir », « étoile noire »…) sans aucun sens. Ajoutez aria-hidden="true" sur chaque glyphe décoratif, ou remplacez-les par des icônes nommées (aria-label) si les tuiles deviennent des commandes. Tactile : un tap émule un mousemove — les tuiles proches sautent vers le point touché et y restent jusqu'au mouseleave suivant.

Compatibilité navigateur

JavaScript ES5 (var, fonctions classiques, forEach sur NodeList), getBoundingClientRect, Math.atan2. CSS : transform, transition avec cubic-bezier, will-change, flex-wrap. Zéro dépendance — le plus rétro-compatible des curseurs du catalogue.

Chrome 51+✓ Complet
Firefox 50+✓ Complet
Safari 10+✓ Complet
Edge 16+✓ Complet
Mobile iOS✓ Saut au tap, pas d'attraction
Android Chrome✓ Saut au tap, pas d'attraction

Sans JavaScript, cinq tuiles statiques correctement disposées : rien ne manque visuellement. Sans transitions CSS (navigateurs très anciens), les tuiles sautent d'une position à l'autre à chaque événement. NodeList.forEach est absent avant Chrome 51 / Firefox 50 / Safari 10 : le script s'arrête sur une erreur, tuiles statiques.

Le code

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

index.html — structure
<div class="magnetic-zone" data-magnetic-zone>
  <div class="magnetic-item" data-magnetic-el>♦</div>
  <div class="magnetic-item" data-magnetic-el>★</div>
  <!-- … 5 au total, une par tuile … -->
</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
Rayon d'attraction (JS — f = 120) 120 px Distance pointeur-centre à partir de laquelle une tuile réagit. 80 = seule la tuile survolée bouge ; 200 = toute la rangée ondule.
Force maximale (JS — 25 * (1 - i / f)) 25 px Translation au contact. 12 = frémissement ; 40 = tuiles qui se chevauchent si l'écart est inférieur.
Agrandissement (JS — 1 + .15 * (1 - i / f)) ×1,15 Échelle au contact. .3 pour un dock qui gonfle ; 0 pour une pure translation.
Halo (JS — rgba(99,102,241, .3 + .4 × …), flou 20 + force) indigo, 20 à 45 px Couleur et taille de l'ombre portée. Lisez une variable CSS par tuile (getComputedStyle(t).getPropertyValue('--glow')) pour un halo assorti au dégradé.
Transition (CSS — transform .3s cubic-bezier(.23, 1, .32, 1)) 300 ms, sortie élastique Lissage du mouvement. Ajoutez box-shadow .3s pour supprimer le saut du halo au bord des 120 px ; .15s pour un suivi plus nerveux.
Point de mesure (JS — t.__repos) position au repos, mesurée une fois Centre relevé transform neutralisé, mémorisé en décalage par rapport à la zone : la tuile se pose sous le pointeur sans osciller et le défilement ne décale rien. Si la disposition change après coup (redimensionnement, tuile ajoutée), remettez t.__repos = null sur chaque tuile — la mesure suivante repart de zéro.
Couleurs des tuiles (CSS — .magnetic-item:nth-child(n)) 5 dégradés 500 Un dégradé par position. Les nuances 600/700 (#4f46e5, #db2777, #0e7490, #b45309, #047857) donnent ≥ 4,6:1 au glyphe blanc.

FAQ

Au premier mousemove sur la zone, pour chaque tuile : le transform en ligne est mis à none, la boîte lue par getBoundingClientRect(), ses décalages par rapport à la zone et sa taille stockés dans t.__repos, puis le transform rétabli. Les événements suivants ne relisent jamais la boîte translatée — c'est ce qui évite l'oscillation qu'entraînerait une mesure sur la tuile déjà déplacée (le vecteur s'inverserait dès que la tuile dépasse le pointeur). Les décalages étant relatifs à la zone, un défilement de page ne fausse rien. En revanche, si les tuiles se réorganisent après cette première mesure (fenêtre redimensionnée, flex-wrap qui passe à la ligne, tuile ajoutée), les décalages mémorisés sont périmés et l'attraction vise l'ancien emplacement : remettez t.__repos = null sur chaque tuile dans un écouteur resize, ou après toute modification du DOM.
La transition CSS ne couvre que transform. À la frontière des 120 px, box-shadow passe sans étape de none à 0 0 20px rgba(99,102,241,.3) (mesuré à 119 px), et retombe à none en sortant. Ajoutez box-shadow .3s à la transition de .magnetic-item : le flou et l'opacité s'interpolent, et le halo naît en douceur. Le coût d'une box-shadow animée sur cinq tuiles est négligeable.
Oui : le JS cible [data-magnetic-el], n'importe quel élément convient ; gardez display: flex et le centrage pour le glyphe, et ajoutez un :focus-visible. Quant à la hauteur : .magnetic-zone { height: 100% } se résout en auto quand le parent n'a pas de hauteur définie (un min-height ne compte pas) — mesuré 100 px de haut dans un aperçu de 300 px, et l'attraction ne se déclenche que dans cette bande. Donnez au parent une hauteur, ou à la zone un min-height ou un padding vertical généreux.