Cursor Spotlight Mask
A soft aperture follows the cursor and reveals a completely different layer hidden under the visible one, stretching into a lens streak on the axis of movement and springing back with an elastic settle.
About This Effect
A cursor-driven reveal effect that stacks two complete compositions in one box and shows the hidden one only through a soft, moving aperture. The surface layer says one thing, the layer beneath it says the opposite, so the reveal reads as a hidden truth rather than a highlighter.
The aperture is not a fixed circle. One input drives four visible consequences at once: it trails the pointer with an eased lag, grows with pointer speed, stretches along the axis of movement so a fast horizontal whip draws it into a lens streak, and drags a thin accent rim a beat behind its centre while the revealed layer parallaxes against the surface.
Everything springs back to the rest circle on an elastic ease, overshooting rather than snapping. On touch devices the aperture wanders on its own slow path and a tap springs it to the tapped point, and a real Reveal everything button opens the aperture over the whole composition for keyboard users and anyone who would rather just read both.
What's Included
- Two stacked compositions revealed through one moving aperture, driven entirely by mask-position and mask-size so the gradient is never rebuilt
- Velocity-reactive aperture: radius grows with pointer speed and stretches on the axis of movement, with a counter-squash that makes a whip read as a lens rather than a bigger circle
- Elastic settle (elastic.out) that overshoots the rest circle and springs back, never a linear snap
- Accent rim on its own slower gsap.quickTo, so it smears behind the aperture and lands last
- Parallax on the revealed layer so the two compositions never feel like one flat image
- Touch answer: the aperture drifts along a slow Lissajous path and a tap springs it to that point with an elastic settle
- Real Reveal everything button with maintained aria-pressed that opens the aperture over the whole composition
- GSAP core only with no plugins, every value set from data attributes, and a graceful floor: no JavaScript, no mask support, or reduced motion leaves both compositions as stacked, fully readable blocks
Perfect For
- Agency and portfolio hero sections with a claim on the surface and the honest version underneath
- Product pages revealing a before and after, or a spec sheet hidden under a lifestyle shot
- Editorial and campaign pages where the interaction itself carries the joke or the reveal
- Pricing or comparison blocks that expose the small print through the aperture
- Case study cards on studio sites that reward the visitor for moving the cursor
How It Works
The beneath layer carries a single radial-gradient mask with a fixed opaque stop, and the script only ever writes mask-position and mask-size (plus their -webkit- prefixed twins) as style strings, so the browser reuses one mask image and never re-parses a gradient. Aperture position is eased with gsap.quickTo onto a proxy object, and the per-frame velocity is measured from that eased centre rather than the raw pointer, which is what lets a fast whip build a stretch.
Velocity feeds three derived channels through clamped lerps: a radius boost, and per-axis stretch factors that scale the two mask-size axes while mildly squashing the other. When the aperture stops, a single gsap.to with an elastic.out ease returns all three to zero with overshoot; a second, slower pair of quickTo drivers moves the accent rim so it trails the centre.
gsap.matchMedia splits fine-pointer tracking from a coarse-pointer branch, where a Lissajous drift path and a tap-driven elastic tween replace the cursor, while the reduced-motion branch drops the masking entirely so both compositions sit in normal flow.