Curseurs✨ Premium

Cursor Text Reveal

Un titre de 32 px réduit à un contour blanc à 30 %, doublé par un ::before en texte plein que clip-path: circle(50px) découpe à la position du pointeur (transition 100 ms) : le mot n'apparaît que sous le cercle et disparaît à la sortie, le cercle étant renvoyé à −100 px.

JSClip-path

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

Hero d'un studio créatif — nom en contour, révélé au passage — Cursor Text Reveal exemple 1

① Hero d'un studio créatif — nom en contour, révélé au passage

QuandPage d'accueil d'un studio : le nom en très grand sur deux lignes, une accroche en dessous.
PourquoiLe contour affirme la typographie sans l'imposer ; le plein n'apparaît que sous la main du visiteur. Le nom est repris en clair dans l'accroche et le menu : rien n'est perdu pour qui ne survole pas.
RéglagesTexte 72 px sur deux lignes (largeur du conteneur fixée), rayon 80px, contour rgba(255,255,255,.35), content: attr(data-text) / "".
Page 404 — le code d'erreur se dévoile — Cursor Text Reveal exemple 2

② Page 404 — le code d'erreur se dévoile

QuandPage introuvable : un grand « 404 », une phrase d'explication, un lien de retour.
PourquoiUne 404 supporte le jeu : le chiffre creux invite à balayer, le plein se révèle. L'explication et le lien restent en texte ordinaire, donc la page fait son travail sans survol.
RéglagesTexte 140 px, rayon 100px, contour remonté à rgba(255,255,255,.45) (4,5:1) pour que le code reste lisible au repos.
Carton d'invitation — mot révélé à la lumière — Cursor Text Reveal exemple 3

③ Carton d'invitation — mot révélé à la lumière

QuandPage d'événement privé : un mot-clé en capitales espacées, la date, le lieu.
PourquoiLe geste évoque une encre qui apparaît sous la lampe ; en doré sur noir, le contour dessine déjà le mot et le plein le fait briller. Le détail utile (date, lieu) est en clair.
RéglagesLettrage letter-spacing: .3em, contour rgba(251,191,36,.5), plein #fde68a, rayon 70px, transition .15s.

Comment ça marche

La zone #zone-text-reveal (fond #0a0a0f en style inline) contient .cursor-reveal-container avec l'attribut data-text="REVEAL ME" et le même texte comme contenu. Le conteneur porte le texte « fantôme » : font-size: 2rem, font-weight: 800, color: transparent, -webkit-text-stroke: 1px rgba(255,255,255,.3), user-select: none. Son ::before porte le texte plein : content: attr(data-text), position: absolute; inset: 0, color: #fff, -webkit-text-stroke: 0, et surtout clip-path: circle(50px at var(--cursor-x, -100px) var(--cursor-y, -100px)) avec transition: clip-path .1s.

Le JS écoute mousemove sur la zone, pas sur le texte, mais calcule les coordonnées par rapport au conteneur de texte : rect = textRevealContainer.getBoundingClientRect(), x = clientX − rect.left, puis setProperty('--cursor-x', x + 'px') sur le conteneur. mouseleave renvoie les deux variables à -100px. Mesuré : clip-path: circle(50px at 19px 18.5px) pendant le survol, circle(50px at -100px -100px) après la sortie — le cercle est alors entièrement hors de la boîte du texte, plus rien de plein n'est visible.

Deux calques du même mot, superposés au pixel : le contour est le vrai texte, le plein est le pseudo-élément. inset: 0 lui donne la boîte exacte du conteneur, et comme il hérite de la police, de la graisse et de la taille, les glyphes tombent aux mêmes positions. Condition absolue : data-text doit être identique au contenu, sinon les deux textes se désalignent. La transition de 100 ms sur clip-path fonctionne parce que les deux extrémités sont des circle() de même rayon : le navigateur interpole le centre.

La zone écoutée, .cursor-zone, pose width: 100% et 300 px de haut : elle remplit son parent, et l'écouteur couvre toute cette surface, pas seulement la colonne du texte (186 px de large) — les coordonnées, elles, restent calculées par rapport au conteneur de texte. Pas de cursor: none, pas de requestAnimationFrame (0 appel/s mesuré) : la flèche système reste, le cercle est collé au pointeur, le seul lissage est la transition CSS.

Accessibilité

  • Le titre est lu deux fois : le contenu généré par CSS est exposé aux technologies d'assistance, et l'arbre d'accessibilité de Chromium montre deux nœuds StaticText « REVEAL ME » — le vrai texte et le ::before. Utilisez la syntaxe de texte alternatif content: attr(data-text) / "" (Chrome 77+, Safari 17.4+, Firefox 128+) pour que le pseudo-élément soit ignoré par les lecteurs d'écran.
  • Contraste au repos : le contour de 1 px à rgba(255,255,255,.3) compose #545457 sur #0a0a0f, 2,6:1, et il est creux — le mot est illisible sans survol, par construction. Le texte révélé fait 19,8:1. Si le titre porte du sens, montez le contour à rgba(255,255,255,.5) (5,3:1) ou affichez le plein sans survol via @media (hover: none) et (prefers-reduced-motion: reduce) : clip-path: none.
  • Tactile : un tap émule un mousemove puis, au tap suivant hors zone, un mouseleave — l'utilisateur voit un cercle de 50 px figé au point touché, jamais le mot entier. Clavier : rien ne se révèle. D'où l'intérêt du repli @media (hover: none) { .cursor-reveal-container::before { clip-path: none } }.
  • user-select: none empêche de sélectionner et copier le titre, même révélé ; retirez la déclaration si le texte doit rester copiable. Le pseudo-élément, lui, n'est jamais sélectionnable — c'est le contenu réel qui compte pour la copie.
  • Zoom et reflow : font-size: 2rem suit le zoom du navigateur, mais le rayon de 50 px et les positions en pixels ne suivent pas — à 200 %, le cercle couvre deux fois moins de texte. Exprimez le rayon en em (circle(1.6em …)) pour qu'il grandisse avec la police.

Compatibilité navigateur

Requiert clip-path: circle() non préfixé avec interpolation, -webkit-text-stroke (supporté sous ce nom par tous, Firefox 49+ inclus), la propriété raccourcie inset et les variables CSS. JS ES2015. Zéro dépendance.

Chrome 87+✓ Complet
Firefox 66+✓ Complet
Safari 14.1+✓ Complet
Edge 87+✓ Complet
Mobile iOS✓ Cercle figé au point du tap
Android Chrome✓ Cercle figé au point du tap

Sans clip-path (navigateurs antérieurs à 2017), le texte plein s'affiche en entier et en permanence : un titre blanc ordinaire, lisible. Sans JavaScript, les variables restent à −100 px : seul le contour est visible, rien ne se révèle. Sans inset (Safari avant 14.1, Firefox avant 66), le pseudo-élément perd sa boîte et se place en haut à gauche du conteneur, désaligné du contour.

Le code

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

index.html — structure
<div class="cursor-zone" id="zone-text-reveal" style="background: #0a0a0f;">
  <div class="cursor-reveal-container" data-text="REVEAL ME" id="textRevealContainer">REVEAL ME</div>
  <span class="info-text" style="color: rgba(255,255,255,0.5);">Revelation du texte</span>
</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 du cercle (CSS — circle(50px …)) 50 px Surface révélée autour du pointeur. À 32 px de texte, 50 px dévoilent deux ou trois lettres ; 120px révèle un mot entier. En em, le rayon suit la taille de police.
Contour (CSS — -webkit-text-stroke: 1px rgba(255,255,255,.3)) 1 px à 30 % Visibilité du texte fantôme. .5 = 5,3:1, lisible au repos ; 0 = texte invisible jusqu'au survol (le plein devient la seule trace).
Transition (CSS — ::before clip-path .1s) 100 ms Retard du cercle sur le pointeur. .3s donne une traîne visible ; 0 colle le cercle. Interpolable uniquement entre deux circle() de même forme.
Texte (HTML — contenu + data-text) REVEAL ME Les deux doivent rester identiques, y compris espaces et casse ; sinon contour et plein se désalignent. Pour un texte traduit, changez les deux.
Taille et poids (CSS — font-size: 2rem; font-weight: 800) 32 px, 800 Le ::before hérite des deux : changez-les sur le conteneur seulement. Une graisse forte est nécessaire pour qu'un contour de 1 px reste lisible.
Position de repos (JS — '-100px' dans mouseleave ; CSS — replis var(--cursor-x, -100px)) −100 px Où part le cercle quand la souris sort. Pour un texte de plus de 100 px de haut, éloignez davantage (-200px) pour que le cercle sorte tout à fait de la boîte.
Couleur du plein (CSS — ::before color) #fff Couleur révélée. Un dégradé se pose avec background-clip: text sur le pseudo-élément ; la découpe clip-path s'applique par-dessus sans conflit.

FAQ

Presque toujours de data-text : le ::before affiche attr(data-text), le contour affiche le contenu HTML ; si l'un a été modifié sans l'autre (traduction, espace, casse), les deux mots ne coïncident plus. Deuxième cause : une largeur différente qui change le retour à la ligne — le pseudo-élément prend la boîte du conteneur via inset: 0, donc fixez la largeur du conteneur si le texte s'étale sur plusieurs lignes. Troisième : un padding sur le conteneur, que inset: 0 ignore — mettez-le sur le parent.
Le contenu généré par CSS fait partie du nom accessible (mesuré : deux nœuds « REVEAL ME » dans l'arbre d'accessibilité de Chromium). La syntaxe content: attr(data-text) / "" déclare un texte alternatif vide : le pseudo-élément reste affiché mais n'est plus lu (Chrome 77+, Safari 17.4+, Firefox 128+). Les navigateurs qui ignorent la syntaxe ignorent toute la déclaration — gardez la ligne content: attr(data-text) juste avant comme repli.
Le JS ne traite qu'un couple d'ids (zone-text-reveal, textRevealContainer). Pour plusieurs instances, bouclez sur document.querySelectorAll('.cursor-reveal-container') et écoutez mousemove sur chaque conteneur ou sur un parent commun, en recalculant getBoundingClientRect() du conteneur concerné à chaque événement. Pour la zone d'écoute, c'est déjà prévu : l'écouteur est sur la zone, pas sur le texte, et la règle livrée lui donne width: 100% — toute la surface du parent réagit. Réduisez ou agrandissez cette zone à volonté : le cercle reste calculé par rapport au texte.