Liquid Lens Magnifier
A round 80 px glass lens replaces the cursor: under it, the content is truly magnified twice — an enlarged copy follows the pointer to the pixel — and brightened (brightness ×1.5, contrast ×1.1), with a mouse or a finger, and the zoom set by a single CSS variable.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Glassmorphism has 47 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .lg-lens-scene area (260 × 170 px, slate gradient #0f172a → #1e293b, cursor: none) holds the content to examine, .lg-lens-text, and the lens .lg-lens: an 80 px disc centered on the pointer by transform: translate(-50%,-50%), a 1.5 px border in 20% white, an inner ring as ::after, a radial gradient highlight and an indigo glow. It fades in over 0.2 s (opacity) when the pointer enters the area and disappears when it leaves.
The magnification comes from a copy. On load, the script clones .lg-lens-text, marks the copy aria-hidden, gives it the .lg-lens-copy class and puts it inside the lens, which clips whatever overflows (overflow: hidden on a disc). On each pointermove, the copy takes the area's size and receives translate(r − x·z, r − y·z) scale(z) (origin top left), where r is the lens's inner radius and z the zoom read from the --lens-zoom variable (2 by default): the targeted point lands exactly at the center of the disc. Measured, pointer at (95, 80): matrix(2, 0, 0, 2, −151, −121) in Chromium 151 and Firefox 153, (−151.5; −121.5) in WebKit 26.5. The copy carries filter: brightness(1.5) contrast(1.1): the magnified area is also brightened. If the content has no background of its own, the copy gets the area's, so the unmagnified text behind does not show through.
The lens magnifies through a copy of the content, and that is the only clean way: a backdrop-filter can only change the colors of what lies behind it, not enlarge it. The disc's backdrop-filter: blur(1px) brightness(1.5) contrast(1.1) therefore brightens the area, and the enlarged, opaque copy described above does the magnifying on top. Tracking uses pointer events (pointerenter, pointermove) and touch-action: none on the area, so mouse and finger are handled alike: measured in Chromium with emulated touch, the lens follows a sliding finger (active at 120 × 90 px) and disappears when it lifts. No “edge distortion”: it is a clean lens, with no warping around the rim.
Accessibility
- Content first: the lens does not replace the content, it enlarges it on screen. The original text stays in the page, read normally by screen readers; the copy is marked
aria-hidden="true"so it is not read twice. If your content containsids, the copy duplicates them: remove them from the magnified content or use classes. - Keyboard: the lens only follows the pointer; there is nothing for the keyboard. For an image where detail matters (a plan, an X-ray, a technical photo), also offer a “zoom” button that opens the image full screen, or native pinch zoom — the lens is a convenience, not the only way to the detail.
- Hidden cursor:
cursor: nonehides the arrow inside the area; the disc replaces it. For users less at ease with a mouse, keep a marker:cursor: crosshairleaves a thin cross at the center of the lens. - With a finger:
touch-action: nonestops the browser from scrolling the page when the gesture starts inside the area — needed for the lens to follow the finger, but on a phone, an area filling the whole screen blocks scrolling. Leave margin around it, or restrict the effect to fine-pointer screens (@media (pointer: fine)). - Motion and contrast: only the fade-in is animated (0.2 s); the lens only moves with the pointer. The 50% white demo text measures 5.2:1 on
#0f172aand 4.8:1 on#1e293b; brightened under the lens, it gains more.
Browser compatibility
JS: pointer events (pointerenter, pointermove, pointerleave), cloneNode, getComputedStyle to read --lens-zoom. CSS: transform, filter, overflow: hidden on a disc, touch-action, backdrop-filter (kept, covered). Zero dependencies. Measured in Chromium 151, Firefox 153 and WebKit 26.5: same ×2 magnification, same framing, no console error. Touch measured in Chromium with mobile emulation; iOS Safari supports touch-action: none since version 13.
Without pointer events (Safari before 13), the lens never appears: the content stays displayed normally, without magnification. Without filter, the magnified area is not brightened but is still enlarged. Nothing breaks the page.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="demo-preview">
<div class="lg-lens-scene" data-lens-scene="">
<div class="lg-lens-text">…</div>
<div class="lg-lens" data-lens="">
<!-- enlarged copy of .lg-lens-text, added by the script -->
</div>
</div>
</div>
The full HTML + CSS + JS of Liquid Lens Magnifier, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Zoom (CSS — .lg-lens-scene { --lens-zoom: 2 }) |
×2 | Read once on load. 1.5 for a discreet lens, 3 or 4 for technical detail (the enlarged content stays sharp: it is HTML, not a stretched picture — unless your content is itself a low-resolution image). |
Lens size (CSS — .lg-lens { width: 80px; height: 80px }) |
80 px | 120-150 px on a large image, to see a whole word; the framing recalculates itself (the script reads the disc's real size). Keep width and height equal. |
Treatment of the magnified area (CSS — .lg-lens .lg-lens-copy { filter: brightness(1.5) contrast(1.1) }) |
brightness ×1.5, contrast ×1.1 | For a light document (paper, parchment): contrast(1.5) brightness(.85), otherwise the background turns white. For metal or an already light photo: brightness(1.2) contrast(1.25). filter: none for a lens that magnifies without changing anything. |
Examined content (HTML — .lg-lens-text) |
a 9.6 px paragraph in 50% white, centered | Put anything in it: an <img>, a plan, a table. Everything inside is copied into the lens on load. The background can sit on .lg-lens-text (kept as is) or on the area (then copied under the copy). |
Cursor (CSS — .lg-lens-scene { cursor: none }) |
hidden | crosshair keeps a thin cross at the center of the lens, useful for precise aiming; zoom-in announces the feature on hover. |
Glass look (CSS — .lg-lens: border, box-shadow, background) |
1.5 px border in 20% white, 20 px indigo glow, radial highlight | A more opaque 2-3 px border sets the lens apart on a light background; a stronger black box-shadow (0 6px 20px rgba(0,0,0,.4)) makes it float above the document. |
FAQ
backdrop-filter alone would only brighten what lies behind the disc: the area would be lighter, never larger (measured, no scaling). CSS alone cannot enlarge what is behind an element; the only clean way is to draw the content a second time, enlarged, inside the lens. That is what the script does: a copy, re-framed on every movement so the targeted point sits at the center. There is no warping at the edge of the disc: for real glass refraction, see Liquid Glass Refraction (fx-0788), which distorts the background with an SVG filter but does not magnify.zone.querySelector('.lg-lens-copy').remove()) and run the script's block again on that area — wrap it in a function taking the area as argument, called once on load and again after each update..lg-lens-text at its display size (for example width: 100%) while keeping the file in high definition: at ×2 or ×3, the copy shows the pixels the screen was not showing, and the detail is sharp. The browser does not download the image twice (same address, same cache). On a Retina screen, a file twice the display size gives a perfectly sharp ×2; beyond that, the photo blurs like any enlarged image.