Spotlight Cursor
Un halo indigo de 300 px — radial-gradient à 40 % d'opacité fondu à 70 % — dessiné par un ::before centré sur le pointeur en pixels, invisible au repos et fondu en 300 ms au survol. Le curseur natif reste : c'est un éclairage d'ambiance qui suit la main, pas un curseur de remplacement.
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
Le conteneur #zone-spotlight-container (.spotlight-container) est position: relative, fond #0a0a0f, overflow: hidden. Son ::before porte tout l'effet : 300 × 300 px, background: radial-gradient(circle, rgba(99,102,241,.4) 0, transparent 70%), left: var(--cursor-x, 50%), top: var(--cursor-y, 50%), transform: translate(-50%, -50%) pour le centrer sur ce point, pointer-events: none, opacity: 0 et transition: opacity .3s. Le texte .spotlight-text est position: relative; z-index: 1 pour rester au-dessus du halo.
Le JS est un seul mousemove : x = clientX − rect.left, y = clientY − rect.top, puis setProperty('--cursor-x', x + 'px') et '--cursor-y' sur le conteneur. Des pixels relatifs au conteneur (mesuré : --cursor-x: 77.7px, --cursor-y: 120px pour un pointeur à ces coordonnées). Pas de requestAnimationFrame, pas de lissage : le halo est collé au pointeur, et rien ne tourne hors survol (0 appel rAF/s mesuré). Chaque événement déplace left/top d'un pseudo-élément — un relayout limité à lui.
L'apparition est du CSS pur : .spotlight-container:hover::before { opacity: 1 }, fondu de 300 ms à l'entrée comme à la sortie. Les variables ne sont jamais remises à zéro (mesuré : 77.7px encore après la sortie) : à la prochaine entrée le halo se rallume là où il était jusqu'au premier mouvement. Le dégradé atteint transparent à 70 % du rayon de 150 px, soit ≈ 105 px : le disque visible fait environ 210 px, découpé par l'overflow: hidden aux bords du conteneur. Au cœur, rgba(99,102,241,.4) sur #0a0a0f compose #2e2f69 — 1,6:1 face au fond, une lueur, pas une lampe.
Deux points d'intégration. La règle .cursor-zone pose width: 100% : la zone remplit son parent et le halo de 300 px a la place de se déployer — sans cette largeur, dans un parent flex centré, la zone se rétracterait à celle de son texte (261 px mesurés pour un aperçu de 760 px). Aucun cursor: none : la flèche système reste visible.
Accessibilité
- Contraste du texte :
rgba(255,255,255,.7)sur#0a0a0f= 9,8:1, et 6,8:1 même au cœur du halo (fond composé#2e2f69) — lisible partout. Le.info-textàrgba(255,255,255,.5)= 5,3:1 pour 14 px, conforme. - Le halo lui-même fait 1,6:1 face au fond : c'est un ornement, pas un composant, et il ne doit porter aucune information (WCAG 1.4.11 ne s'applique pas tant qu'il reste décoratif). N'en faites pas l'unique indice d'une zone interactive.
- prefers-reduced-motion : le seul mouvement autonome est le fondu de 300 ms ; le halo suit la main, il n'est pas animé. Optionnel mais propre :
@media (prefers-reduced-motion: reduce) { .spotlight-container::before { transition: none } }. - Tactile et clavier : sur écran tactile, un tap émule
mousemoveet:hover— le halo s'allume au point touché et reste jusqu'au tap suivant. Au clavier, rien ne se produit. Acceptable puisque l'effet est décoratif ; s'il souligne des liens, gardez-leur un:focus-visibleexplicite. - Lecteurs d'écran : le
::beforeacontent: "", rien n'est annoncé. Si vous placez des liens dans le conteneur, donnez-leurposition: relative; z-index: 1comme.spotlight-textpour qu'ils restent au-dessus du halo, et vérifiez que l'overflow: hiddenne rogne pas leur anneau de focus près des bords.
Compatibilité navigateur
Requiert les propriétés personnalisées CSS avec setProperty, un ::before positionné et transformé, et radial-gradient(). JS ES2015 (fonctions fléchées, const). Zéro dépendance.
Sans JavaScript, le halo reste au centre (repli 50 %/50 %) et s'allume toujours au survol : une vignette statique, cohérente. Sans variables CSS (navigateurs antérieurs à 2016), left/top deviennent invalides et le pseudo-élément se place en haut à gauche du conteneur.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="cursor-zone spotlight-container" id="zone-spotlight-container">
<span class="spotlight-text">Deplacez le curseur pour illuminer</span>
<span class="info-text" style="color: rgba(255,255,255,0.5);">Effet spotlight CSS</span>
</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 |
|---|---|---|
| Taille du halo (CSS — ::before width/height) | 300 px | Diamètre de la boîte du dégradé ; le disque visible fait 70 % de cette valeur (≈ 210 px). Le translate(-50%, -50%) garde le centre sous le pointeur quelle que soit la taille. |
Couleur et intensité (CSS — rgba(99,102,241,.4)) |
indigo à 40 % | Teinte et force de la lueur au centre. .6 devient une lampe ; une couleur chaude (rgba(251,191,36,.35)) donne un projecteur de scène. |
Fondu du dégradé (CSS — transparent 70%) |
70 % | Rayon auquel le halo s'éteint. 50% = disque compact à bord doux ; 100% = lueur qui remplit toute la boîte de 300 px. |
| Transition (CSS — ::before transition) | opacity .3s | Durée de l'allumage/extinction au survol. Ajoutez left .15s, top .15s pour un halo qui suit avec un léger retard — des pixels s'interpolent, contrairement aux pourcentages non typés. |
Position de repli (CSS — var(--cursor-x, 50%)) |
50 % / 50 % | Où se trouve le halo avant le premier mouvement et sans JS. 0% le range dans un coin ; la valeur reste utilisée tant que le JS n'a pas écrit la variable. |
| Mode de fusion (CSS — ::before, à ajouter) | normal | mix-blend-mode: screen éclaircit ce qu'il survole (images, grilles) au lieu de poser une couche colorée ; plus-lighter pour une lueur additive plus franche. |
| Largeur du conteneur (CSS — .cursor-zone width) | 100 % | La zone prend toute la largeur de son parent. Une largeur fixe (480px) borne le halo à une carte ; évitez auto dans un parent flex centré — la zone se rétracte à la largeur de son texte (261 px mesurés sur 760) et l'overflow: hidden rogne le halo de 300 px de toutes parts. |
FAQ
.cursor-zone livrée pose width: 100% : la zone remplit son parent. Si elle se rétracte à la largeur de son texte, c'est que cette déclaration a été retirée ou écrasée par une règle plus spécifique — dans un parent flex centré, la zone mesure alors 261 px pour 760 px disponibles, et l'overflow: hidden coupe le halo de 300 px aux bords. Vous pouvez aussi appliquer l'effet à un bloc qui a déjà sa taille (section, carte). Les trois conditions à conserver : position: relative, overflow: hidden et une hauteur.left et top reçoivent des longueurs interpolables. Ajoutez left .15s ease-out, top .15s ease-out à la transition du ::before ; le halo glisse alors vers chaque nouvelle position. Pour un lissage plus physique (facteur par frame), reprenez la boucle requestAnimationFrame de Dot Follower (fx-0197).#zone-spotlight-container, par id. Remplacez getElementById par document.querySelectorAll('.spotlight-container').forEach(zone => { … }) en posant l'écouteur sur chaque zone : le CSS, déjà écrit sur la classe, s'applique à toutes les instances. Chaque conteneur écrit ses propres variables, il n'y a pas de collision.