Glassmorphism✨ Premium

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.

JSMouseLens

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

Deciphering a scanned old map — the departmental archives' website — Liquid Lens Magnifier example 1

① Deciphering a scanned old map — the departmental archives' website

WhenA genealogist moves the lens over an 1827 Napoleonic land register plan, faded ink: plot numbers, field names, the line of the road.
WhyPlot numbers are 11 px on screen and the ink has faded: they need enlarging without losing the rest of the plan, which tells you where you are. The ×2 lens isolates a number, a field name or a carmine house, while the neighboring plots stay visible around the disc — you read without zooming the whole map or losing your place.
SettingsArea enlarged to 540 × 316 px under a 44 px archive toolbar; 130 px lens; copy filter tuned for light paper (contrast(1.5) brightness(.85) saturate(.9)) instead of the original brightening, which washed out the parchment. The plan is made of HTML plots: the copy follows the same layout.
Spotting a defect on a weld photo — a factory quality control station — Liquid Lens Magnifier example 2

② Spotting a defect on a weld photo — a factory quality control station

WhenThe inspector moves the lens over a high-definition photo of a chassis node: two crossing welds, to be checked before painting.
WhyA 2 or 3 px pore gets lost on a 540 px photo; magnified twice and contrasted, it becomes a sharp dot on the bead's ripples. The inspected area is clearly bounded by the disc, the rest of the part stays in view: you know where you are on the weld, and you can show the spot to a colleague.
SettingsSame 130 px lens; copy filter at brightness(1.2) contrast(1.25) — the original ×1.5 brightness burned out the bead's light metal. Welds and heat tint drawn with gradients, three pores and an incipient crack.
Examining a panoramic X-ray — a dental practice's software — Liquid Lens Magnifier example 3

③ Examining a panoramic X-ray — a dental practice's software

WhenDuring the appointment, the dentist shows the patient the tooth being discussed on the panoramic X-ray displayed on screen.
WhyOn a dark X-ray, the original brightening is exactly what is needed: under the lens, enamel and decay stand out, magnified, without changing the settings of the whole image. The patient follows the spot being discussed instead of searching a gray picture — the disc acts as a pointing finger.
SettingsOriginal code settings for the copy (brightness(1.5) contrast(1.1), ×2); 130 px lens; X-ray drawn with gradients on a #06080b background, decay on tooth 36 and a filling on 26.

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 contains ids, 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: none hides the arrow inside the area; the disc replaces it. For users less at ease with a mouse, keep a marker: cursor: crosshair leaves a thin cross at the center of the lens.
  • With a finger: touch-action: none stops 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 #0f172a and 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.

Chrome 55+✓ Full
Firefox 59+✓ Full
Safari 13+✓ Full
Edge 79+✓ Full
Mobile iOS 13+✓ Full (the lens follows a sliding finger)
Android Chrome 55+✓ Full (the lens follows a sliding finger)

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):

index.html — structure
<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>
Unlock the full code — $9.90/mo incl. tax

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 / propertyDefaultEffect
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

It magnifies, twice by default. A brightness and contrast 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.
The copy is made once, on load. An image that finishes loading later is not a problem (the copy loads the same address), but replaced text, a redrawn map or content loaded on demand are not copied again. The script keeps a reference to its copy, so the simplest fix is: after each change, remove the old copy (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.
Yes, and that is where it helps most. Put the image in .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.