Spotlight Reveal
Un contenu caché dans une zone sombre, révélé sous le curseur par un mask CSS radial : deux variables en pourcentage pilotent simultanément la fenêtre de visibilité et le halo coloré qui éclaire ce qu'elle laisse voir.
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
L'effet repose sur deux couches dans la zone .spotlight-zone. En dessous, le pseudo-élément ::before affiche un texte fantôme à 10 % d'opacité. Au-dessus, le div .spotlight-reveal couvre toute la zone via inset: 0 : il ne cache pas le contenu, il est le contenu — le texte révélé y vit directement. Ce div reçoit simultanément deux radial-gradient pilotés par les mêmes variables --x et --y : un background qui projette un halo indigo (rgba(99, 102, 241, .3), rayon 80 px) et un mask qui découpe une fenêtre circulaire de 100 px. C'est le mask qui fait tout : là où il est noir, l'élément est visible ; là où il est transparent, il disparaît.
L'écouteur mousemove est posé sur #zone-spotlight, pas sur .spotlight-reveal. À chaque mouvement, getBoundingClientRect() relève la géométrie de la zone, et le script calcule les coordonnées en pourcentage : x = ((e.clientX − rect.left) / rect.width) * 100, idem en Y. Ces valeurs sont écrites dans --x et --y via style.setProperty(). Le navigateur recalcule les deux dégradés instantanément — fenêtre et halo bougent ensemble, d'un seul appel. Aucun mouseleave n'est déclaré : quand le curseur quitte la zone, les variables restent figées.
Le choix du mask CSS — plutôt qu'un overlay opaque par-dessus — change la lecture : c'est la couche de contenu qui s'ouvre, pas un couvercle qui se soulève. Ce qui sort du cercle n'est pas rendu, mais reste dans le DOM — le texte caché est accessible aux technologies d'assistance. Les deux rayons — 80 px pour le halo, 100 px pour la fenêtre — sont indépendants. Leur écart de 20 px crée une frange où le mask s'estompe avant que la couleur ne soit nulle : l'illumination paraît fondue, pas coupée net.
Accessibilité
- prefers-reduced-motion : absent du code. Conditionne l'enregistrement de l'écouteur à
window.matchMedia('(prefers-reduced-motion: reduce)')avant tout déploiement — en mode réduit, laisse les variables au repli50% 50%pour un mask centré et statique. - Sur écran tactile, aucun
mousemovene circule : les variables restent aux valeurs par défaut, la fenêtre reste centrée et le contenu y est visible statiquement — repli sain, mais non interactif. Ne fais jamais dépendre une action critique du survol si une partie de ton audience n'a pas de souris. - Sans
mouseleave, le spotlight reste figé à la dernière position quand le curseur sort. Si le contenu masqué est fonctionnel, ajoute un écouteurmouseleavequi remet--xet--yà50%. - Le contenu de
.spotlight-revealest dans le DOM — le mask ne touche pas à l'arbre d'accessibilité. Mais le texte fantôme du::beforeet le texte révélé coexistent pour les technologies d'assistance : si le::beforeporte une instruction, déclare-learia-hidden="true".
Compatibilité navigateur
La propriété mask non préfixée est acquise dans Chrome 120+, Firefox 53+ et Edge 79+. Pour Safari, -webkit-mask est requis jusqu'à 15.3 ; depuis 15.4, l'alias non préfixé fonctionne. Le reste — variables CSS, radial-gradient, inset: 0 — est sans problème. Le JavaScript utilise des fonctions fléchées (ES2015) : IE 11 s'arrête avant le premier événement.
Sans JavaScript, les variables restent au repli <code>50% 50%</code> : la fenêtre reste centrée et statique, le contenu y est visible — un repli sain, pas un écran vide. Pour couvrir Safari avant 15.4, duplique les deux propriétés avec le préfixe <code>-webkit-mask</code> avant <code>mask</code>. Aucune autre dépendance.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
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 |
|---|---|---|
| Rayon du mask (100px dans le CSS) | 100 px | La propriété mask: radial-gradient(circle 100px at …) contrôle la taille de la fenêtre. Augmente cette valeur pour révéler plus d'un coup ; réduis-la pour une fenêtre étroite. Ce rayon est indépendant du halo — garde-les distincts pour conserver la frange de fondu. |
| Rayon du halo coloré (80px dans le background) | 80 px | Pilote uniquement la tache lumineuse. Plus petit que le rayon du mask, il crée une zone éclairée qui s'estompe avant le bord de la fenêtre — l'effet lampe torche. Égalise les deux rayons pour un bord net et coloré. |
| Couleur et opacité du halo (rgba(99, 102, 241, .3)) | indigo à 30 % | Change les 4 valeurs RGBA pour adapter teinte et intensité. 0.1 = lueur quasi invisible ; 0.7 = halo dominant qui colore le contenu. Sur fond clair, un halo sombre donne un effet de lentille inversée. |
| Contenu révélé (nœud texte dans .spotlight-reveal) | texte simple | Remplace le nœud texte par n'importe quel HTML : image, SVG, badge, lien. Le mask s'applique à tout ce qui reste dans .spotlight-reveal. Évite les champs de formulaire — leur zone de clic suit le bord visible du mask, ce qui peut surprendre. |
| Texte fantôme (::before sur .spotlight-zone) | "Déplacez la souris ici" à 10 % d'opacité | Change le content pour une autre instruction, baisse l'opacité à 0.05, ou supprime-le si la zone est auto-explicite. Ce pseudo-élément est distinct de .spotlight-reveal et non affecté par le mask. |
| Comportement au départ du curseur (pas de mouseleave) | figé à la dernière position | Sans mouseleave, le spotlight reste là où le curseur s'est arrêté. Ajoute un écouteur qui remet --x et --y à 50% pour recentrer au départ. Ou passe à -200% pour sortir le mask de la zone et cacher tout le contenu. |
FAQ
mouseleave, fx-0214 recentre au départ ; fx-0214 cache le curseur système (cursor: none), fx-0201 le laisse visible.id via getElementById — une seule paire. Pour plusieurs instances, passe sur querySelectorAll('.spotlight-zone') et boucle en récupérant le .spotlight-reveal enfant et en attachant l'écouteur avec les références locales dans la fermeture. Les variables étant écrites sur le style inline de chaque .spotlight-reveal, les instances sont indépendantes.radial-gradient(), qui les interprète comme un ratio de la dimension de l'élément. Si la zone est redimensionnée, le prochain mousemove relit getBoundingClientRect() et corrige le positionnement — pas besoin de ResizeObserver, contrairement à une approche en pixels.