Curseurs✨ Premium

Cursor Morph

Un curseur personnalisé qui change de forme selon l'élément survolé : losange sur un lien, barre clignotante sur un champ de saisie, cercle par défaut. Chaque état est piloté par un attribut data-morph lu à chaque événement ; la position suit avec un lerp à 15 %.

JSAdaptive

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

Formulaire d'inscription ou de contact — champs hétérogènes — Cursor Morph exemple 1

① Formulaire d'inscription ou de contact — champs hétérogènes

QuandUn formulaire mélange boutons de soumission, champs texte et liens légaux sur la même surface. Le curseur change de forme à chaque type de cible.
PourquoiLe morph encode l'interaction disponible avant le clic : barre clignotante sur un input, losange sur un lien. La zone devient lisible par le curseur seul, sans label supplémentaire.
RéglagesPose data-morph="input" sur chaque <input> et <textarea>, data-morph="link" sur les ancres CGU, data-morph="button" sur le bouton de soumission. Monte le lerp à 0.20.
Barre de navigation — liens et déclencheurs de sous-menu — Cursor Morph exemple 2

② Barre de navigation — liens et déclencheurs de sous-menu

QuandUne nav horizontale mélange liens directs, boutons de sous-menu et zones neutres. L'effet signale la nature de chaque cible sans que l'élément lui-même ne réagisse.
PourquoiLe losange sur les liens et la forme neutre sur les zones non interactives signalent l'action disponible — un retour que les transitions CSS de survol ne peuvent pas reproduire.
RéglagesAttribue data-morph="button" aux déclencheurs de sous-menu, data-morph="link" aux liens directs. Lerp à 0.20 pour une réponse collée sur des cibles de navigation rapide.
Configurateur produit — swatches, quantité et guides de taille — Cursor Morph exemple 3

③ Configurateur produit — swatches, quantité et guides de taille

QuandUne page produit propose des swatches cliquables, un champ de quantité et des liens de guide. Trois types d'éléments, trois comportements attendus du curseur.
PourquoiLe curseur adaptatif encode la nature de chaque zone sans icônes ni tooltips. La décision d'interaction est prise avant le clic, au seul regard du curseur.
RéglagesPose data-morph="button" sur les swatches et les boutons panier, data-morph="input" sur le champ de quantité. Lerp à 0.20 sur de petites cibles — une traîne trop longue nuit à la précision.

Comment ça marche

Dès que le pointeur entre dans #zone-morph, le code pose la classe cursor-active et bascule #cursorMorph de display: none à display: block. C'est la classe cursor-active — que tu dois associer à cursor: none dans ton CSS — qui masque le curseur natif dans la zone. #cursorMorph est un <div> positionné en fixed qui constitue le curseur de remplacement. À la sortie, la classe est retirée, le curseur masqué et ses classes de forme réinitialisées. Le curseur système reprend immédiatement.

À chaque mousemove, le code lit e.target.getAttribute('data-morph') — l'attribut posé sur l'élément directement sous le pointeur. Selon la valeur : la classe morph-link rend #cursorMorph en 12 × 12 px sans border-radius, tourné de 45° — un losange indigo. La classe morph-input applique 2 × 24 px arrondi à 1 px, plus une animation qui maintient l'opacité à 1 pendant la première moitié du cycle, puis la coupe à 0 avec un saut abrupt — le curseur texte natif reproduit. Sans attribut data-morph, aucune classe de morph n'est ajoutée. Avant chaque assignation, className est réinitialisé : aucune classe résiduelle ne subsiste. La transformation est instantanée — un simple échange de classes CSS.

La position suit la souris via un requestAnimationFrame et un lerp à 15 % : à chaque frame, la position courante avance de 15 % de l'écart restant. Le curseur ne colle pas exactement au pointeur — il converge rapidement avec une légère traîne perceptible. Le centrage est recalculé à chaque frame depuis offsetWidth et offsetHeight : quand la forme passe du losange 12 × 12 à la barre 2 × 24, le point d'ancrage se corrige automatiquement.

Accessibilité

  • Masquer le curseur système est une décision d'accessibilité lourde. Certains utilisateurs dépendent d'un curseur OS haute visibilité — grande taille, contraste élevé, forme personnalisée via les préférences système. cursor: none efface toutes ces adaptations sans distinction. Limite sa portée à la zone exacte via la classe cursor-active et ne l'applique jamais sur des champs de formulaire réels ou tout élément où le curseur texte est attendu.
  • prefers-reduced-motion : absent du code. Deux animations coexistent : le lerp permanent via requestAnimationFrame et l'animation cursor-blink sur l'état input. Conditionne l'initialisation de la boucle à window.matchMedia('(prefers-reduced-motion: reduce)') et neutralise l'animation CSS sous la media query correspondante avant tout déploiement.
  • L'effet est 100 % souris. Sur écran tactile, mouseenter et mousemove ne se déclenchent pas : le curseur personnalisé reste masqué, le curseur natif n'est pas affecté. Vérifie que tous les éléments marqués data-morph restent interactifs sans l'effet : l'attribut ne remplace ni un état :focus ni un comportement clavier.
  • Le curseur personnalisé est un <div> purement visuel, sans rôle ARIA ni contenu textuel. Les technologies d'assistance l'ignorent. Ne porte jamais d'information fonctionnelle par la forme du curseur : une barre clignotante n'indique pas à un lecteur d'écran qu'un champ est actif, et un losange ne se substitue pas à un état :focus-visible sur le lien lui-même.

Compatibilité navigateur

Repose sur addEventListener, getAttribute, classList, requestAnimationFrame et offsetWidth — des APIs disponibles partout depuis des années. Le plancher réel est la syntaxe ES2015 (fonctions fléchées, IIFE) : IE 11 s'arrête sur une erreur de syntaxe avant d'entrer dans la zone. Aucun préfixe CSS requis.

Chrome 80+✓ Complet
Firefox 75+✓ Complet
Safari 13+✓ Complet
Edge 80+✓ Complet
Mobile iOS✓ Inerte (tactile)
Android Chrome✓ Inerte (tactile)

Sans JavaScript, <code>#cursorMorph</code> reste en <code>display: none</code> et le curseur système s'affiche normalement — aucun contenu fonctionnel ne dépend du curseur personnalisé. Point de vigilance : si <code>cursor: none</code> est déclaré en CSS sans être conditionné à la classe <code>cursor-active</code>, le curseur natif disparaît même sans l'effet. Écris toujours la règle sur <code>.cursor-active { cursor: none; }</code>, jamais sur la zone seule.

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
Facteur de lerp (0.15 dans le JS) 0.15 Règle la traîne. 0.05 = arrivée nettement en retard. 0.30 = presque collé. 1.0 = suivi instantané. Sur de petites cibles, monte à 0.20–0.25 pour préserver la précision du clic.
Valeurs attendues par data-morph 'button', 'link', 'input' Le code lit getAttribute('data-morph') et ajoute la classe correspondante. Pour un nouvel état — morph-video sur une zone de lecture par exemple — ajoute la branche dans le mousemove et définis la forme en CSS. Sans attribut, le curseur prend sa forme par défaut.
Forme de l'état link (12 × 12 px, rotate 45°) 12 px × 12 px, tourné de 45° Ces valeurs CSS définissent le losange. Augmente la taille pour un losange plus grand, retire la rotation pour un carré droit. offsetWidth/offsetHeight est relu à chaque frame : modifier les dimensions en CSS ne touche pas au JavaScript.
Forme de l'état input (2 × 24 px, border-radius 1 px) 2 px × 24 px Ces dimensions imitent le curseur texte natif. Augmente la hauteur sur de grandes zones de texte ; épaissit à 4–6 px pour un curseur stylisé. Le recalcul de offsetWidth/offsetHeight garantit le centrage quelle que soit la géométrie choisie.
Couleur partagée (background: #6366f1) #6366f1 (indigo) Les trois classes de morph déclarent chacune leur couleur dans leur règle background. Pour un réglage centralisé, remplace les valeurs en dur par une variable CSS --cursor-color définie en racine et référencée dans chaque sélecteur de morph.
Animation cursor-blink (1 s, coupure abrupte à 50 %) 1 s, opaque 0–50 %, invisible 51–100 % Les keyframes coupent l'opacité à 0 à mi-cycle — pas de fondu progressif. Pour un clignotement plus nerveux, descends à 0.6 s ; pour un fondu, réécris les keyframes avec une interpolation linéaire. Ajoute animation: none sous prefers-reduced-motion pour neutraliser le clignotement sans toucher à la forme.

FAQ

Le curseur natif ne disparaît que si tu associes cursor: none à la classe cursor-active dans ton CSS — le JavaScript n'y touche pas. Sans cette règle, le curseur personnalisé se superpose au curseur système et les deux sont visibles simultanément. La séparation est intentionnelle : la portée de cursor: none reste sous ton contrôle, pas codée en dur dans le script.
getAttribute('data-morph') renvoie null si l'attribut est absent. Le code réinitialise alors className à 'custom-cursor cursor-morph' sans ajouter aucune classe de morph : le curseur reprend sa forme par défaut. Cette réinitialisation se produit à chaque mousemove quelle que soit la cible — aucune classe résiduelle d'un survol précédent ne subsiste.
Non — c'est une limite à anticiper. Dès qu'un <input> reçoit le focus et que l'utilisateur tape, mousemove cesse d'être émis si le pointeur est immobile. Le curseur personnalisé se fige à sa dernière position calculée. Assure-toi que cursor: none ne s'applique pas aux champs en production.