Cursor Text
Une étiquette fixe de 12 px décalée de 15 px sous le pointeur, qui réécrit à chaque mouvement les coordonnées locales de la souris — « X: 228 Y: 180 », arrondies au pixel, mesurées depuis le coin haut-gauche de la zone. Le curseur natif est masqué : l'étiquette est le seul repère.
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



Comment ça marche
Un seul élément porte l'effet : #cursorText (.custom-cursor.cursor-text-display). La classe .custom-cursor le sort du flux — position: fixed, pointer-events: none, z-index: 9999, display: none — et .cursor-text-display l'habille : font: 600 .75rem Inter, monospace, couleur #6366f1, fond rgba(99,102,241,.1), bordure 1 px rgba(99,102,241,.3), padding: 4px 8px, border-radius: 4px, white-space: nowrap et surtout transform: translate(15px, 15px), le décalage qui l'écarte du point chaud.
La zone #zone-text reçoit trois écouteurs. mouseenter ajoute la classe cursor-active à la zone — cette fois la règle existe bien dans le CSS livré : .cursor-active { cursor: none !important }, la flèche système disparaît (mesuré : cursor: none) — et passe l'étiquette en display: block. mouseleave défait les deux. mousemove fait le travail : localX = Math.round(clientX − rect.left), idem en Y, puis textContent = `X: ${localX} Y: ${localY}` et left = clientX + 'px', top = clientY + 'px'.
Les coordonnées écrites sont locales à la zone (0, 0 = son coin haut-gauche), recalculées via getBoundingClientRect() à chaque événement — donc justes même si la page a défilé ou si la zone a bougé. Le positionnement, lui, est en coordonnées de fenêtre (clientX/Y, cohérent avec position: fixed). Mesuré : pour un pointeur à (228, 180) dans la zone, le texte lit exactement « X: 228 Y: 180 » et le coin haut-gauche de l'étiquette est à +15 px, +15 px du pointeur ; l'étiquette fait 112 × 24 px.
Ni requestAnimationFrame ni lissage : l'étiquette est collée au pointeur, 0 appel rAF/s hors survol ; le coût par événement est une écriture de textContent (relayout de l'étiquette seule). Le fichier JS livré ne contient que ce bloc (« Zone 9 : Cursor Text »), rien d'un autre effet. La règle .cursor-zone définit localement ses cinq variables : la zone de démo se rend correctement sur une page vierge.
Accessibilité
- Contraste de l'étiquette insuffisant :
#6366f1sur son fond (rgba(99,102,241,.1)composé sur#1a1a24, soit#212238) = 3,5:1 pour un texte de 12 px, sous le seuil AA de 4,5:1. Passez la couleur à#a5b4fc(7,8:1) ou#c7d2fe(10,4:1) ;#818cf8(5,2:1) reste le plus proche de l'original. cursor: nonefait de l'étiquette le seul indicateur du pointeur, or elle est décalée de 15 px en bas à droite : rien ne marque le point chaud exact. Pour une interface de précision, retirez la règle.cursor-active(la flèche revient, l'étiquette l'accompagne) ou ajoutez un::beforede 4 px àtranslate(-15px, -15px)qui repose sur le pointeur.- Lecteurs d'écran : le
divchange de texte à chaque mouvement. Il n'a pas d'aria-live, donc rien n'est annoncé, mais il reste dans l'arbre d'accessibilité : ajoutezaria-hidden="true"pour que les technologies d'assistance l'ignorent tout à fait. Si les coordonnées ont un sens fonctionnel (sélecteur de position), exposez-les aussi dans un champ ou une sortieoutput. - Tactile : mesuré avec émulation tactile, un tap dans la zone affiche l'étiquette figée sur les coordonnées du tap et laisse
cursor-activeen place même après un tap hors zone (pas demouseleave). Conditionnez l'initialisation àmatchMedia('(hover: hover) and (pointer: fine)').matches. - Clavier : aucun équivalent, l'étiquette ne se montre jamais — acceptable tant qu'elle n'apporte qu'un confort. Pas de mouvement autonome :
prefers-reduced-motionn'a rien à désactiver.z-index: 9999: l'étiquette passe au-dessus des modales et des menus ouverts.
Compatibilité navigateur
Requiert ES2015 (gabarits de chaîne, fonctions fléchées, const), classList, getBoundingClientRect et les variables CSS de la zone. Zéro dépendance.
Sans JavaScript, l'étiquette reste en display: none et la flèche système est conservée : la zone est une boîte ordinaire. Sans variables CSS, la zone perd son fond et son arrondi ; l'étiquette, qui n'en utilise aucune, reste intacte.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="cursor-zone" id="zone-text">
Survolez cette zone
<span class="info-text">Texte suiveur</span>
</div>
<!-- Ce div est le curseur lui-même : le script le déplace et le fait
apparaître. Sans lui, le script sort et l'effet ne rend rien. -->
<div class="custom-cursor cursor-text-display" id="cursorText">X: 0 Y: 0</div>
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éfaut | Effet |
|---|---|---|
| Décalage (CSS — .cursor-text-display transform) | translate(15px, 15px) | Position de l'étiquette par rapport au point chaud. Des valeurs négatives la placent au-dessus ou à gauche ; 0 la centre sur le pointeur et masque ce qu'il vise. |
Contenu (JS — cursorText.textContent = …) |
X: ${localX} Y: ${localY} | Ce que lit l'étiquette. Un texte fixe (« Voir », « Glisser ») se pose une fois ; pour des unités, divisez par rect.width pour des pourcentages. |
| Couleur du texte (CSS — .cursor-text-display color) | #6366f1 | 3,5:1 sur le fond livré. #a5b4fc ou #c7d2fe passent le seuil de 4,5:1 sans changer la teinte. |
| Police et taille (CSS — .cursor-text-display font) | 600 .75rem Inter, monospace | Inter n'est pas fournie : la pile retombe sur monospace. Une monospace assumée (ui-monospace, Menlo) évite le saut de largeur quand les chiffres changent. |
| Fond et bordure (CSS — .cursor-text-display background, border) | rgba(99,102,241,.1) / 1px rgba(99,102,241,.3) | Habillage de la pastille. Un fond opaque (#0f0f1a) garantit la lisibilité au-dessus d'une image ; sans bordure, l'étiquette devient un simple texte flottant. |
| Masquage du curseur natif (CSS — .cursor-active) | cursor: none !important | Supprimez la règle pour garder la flèche système et n'ajouter que l'étiquette — recommandé dès que la zone contient des cibles de précision. |
Arrondi des coordonnées (JS — Math.round) |
au pixel | Math.round(x * 10) / 10 pour un dixième de pixel (utile avec un zoom), ou Math.floor pour rester dans la cellule survolée d'une grille. |
FAQ
cursorText.textContent = `X: ${localX} Y: ${localY}` par le texte voulu, ou fixez-le une fois dans le HTML et supprimez cette ligne ainsi que les deux Math.round devenus inutiles. Pour un texte qui dépend de l'élément survolé, lisez e.target.closest('[data-cursor-text]') et copiez son attribut — le reste du code (affichage, position, décalage) ne change pas.#cursorText porte transform, filter, perspective, will-change: transform ou contain: paint : il devient le bloc conteneur de l'élément position: fixed, et left = clientX se mesure alors depuis cet ancêtre, plus depuis la fenêtre. Le code livré place l'étiquette dans la zone de démo ; à l'intégration, déplacez-la juste avant </body>, hors de tout conteneur transformé.rect = zoneText.getBoundingClientRect(), puis clientX − rect.left — (0, 0) est le coin haut-gauche de la zone, indépendamment du défilement. Pour des pourcentages : Math.round((clientX − rect.left) / rect.width × 100). Pour des coordonnées de document : e.pageX et e.pageY directement. Le positionnement de l'étiquette, lui, doit rester en clientX/Y puisqu'elle est fixed.