052

Magnetic Glass Lens

£10 or the whole Vault, £99

A glass loupe that follows the cursor and magnifies the live page beneath it, with refraction wobble, chromatic fringing and 3D tilt momentum.

advanced
6 more details
cursor-effectsphysicsmicro-interaction3d-transformssvg-filtersvelocity-reactive

This demo reads better at your own screen size than in the frame below:

Updated

About this effect

A physical glass loupe that follows the pointer and magnifies the real page content beneath it. Glide slowly and it reads like an optician's instrument: the point under the lens centre stays locked under the glass while a refraction wobble bends the magnified image like a true convex lens.

Read the full effect overview

Whip the cursor and the physics answer: the lens banks in 3D against its momentum, the magnified image splits into red and blue fringing scaled by velocity, and the specular glint swings around the bezel to face the direction of travel. Stop dead and everything settles with a damped elastic wobble.

Built with GSAP core only. The magnifier is live DOM, not a screenshot: the content under the glass is a synchronised clone of the page, so it stays sharp at any zoom and updates with the layout.

Magnetic Glass Lens - GSAP animation effect preview

What's included

8 items
  • One movement vector drives five response channels: smoothed spatial tracking, velocity-banked 3D tilt, RGB channel separation, a rotating bezel glint aimed at the travel heading, and a damped elastic settle
  • True optical magnification: a synchronised full-size clone of the page is scaled and counter-translated so the point under the lens centre always lands on the optical centre
  • Refraction wobble from an SVG feDisplacementMap whose strength is driven by cursor velocity, over a base level that reads as glass texture at rest
  • Chromatic dispersion built from colour-isolated content layers that fade in and spread apart with speed, not a canvas trick
  • Configured live through data attributes: change data-lens-zoom, data-lens-dispersion or data-lens-filter at runtime and the lens follows, which is all a preset switcher needs to do
  • Film-negative mode inverts everything inside the glass, leaving the page untouched
  • Touch support with pointer capture and an idle figure-8 preview orbit that resumes after interaction pauses
  • Keyboard accessible: focusable inspection targets glide the loupe to themselves; reduced motion gets direct positioning with the physics switched off; without JavaScript the page simply renders without the lens

Perfect for

4 use cases
  • Print shops, poster stores and editorial archives where fine detail is the selling point
  • Luxury product pages for horology, jewellery and fabric close-ups
  • Type foundry specimens and design portfolio pieces built around micro-typography
  • Photography and art gallery sites that want inspection to feel physical

How it works

3 sections

The script clones the element marked data-lens-content into the lens aperture three times, one copy per colour channel, sized to match the stage exactly. Each frame, a GSAP ticker lerps the lens toward the pointer, then scales and counter-translates the cloned view so the point under the lens centre maps onto the aperture centre at the current zoom.

The frame-to-frame delta gives a velocity vector. Its magnitude drives the scale attribute of an SVG feDisplacementMap for the refraction wobble and sets the offset and opacity of the red and blue channel layers for the dispersion fringing; its heading angles both the 3D tilt (banking against momentum via rotateX and rotateY) and the conic-gradient glint on the bezel through CSS custom properties.

When speed falls to rest the loop hands off to tracked settle tweens: an elastic.out on the tilt and scale, and quick power2 decays on the fringing, glare and wobble. A MutationObserver watches the stage's data-lens-* attributes so zoom, dispersion and the negative filter can be changed live from outside the script.

Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying Magnetic Glass Lens opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

1. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

2. Add the optical filters once, anywhere inside <body>:

Code snippet omitted: it ships with the download.

3. Mark up the stage, your content, and the lens:

Code snippet omitted: it ships with the download.

4. Add before closing </body> tag:

Code snippet omitted: it ships with the download.

Using It With Your Own Design

What the effect actually requires of your markup:

  • A positioned stage. [data-lens-stage] must be position: relative (or otherwise positioned); the lens and the magnified view are placed in its coordinate space.
  • One content root. [data-lens-content] is cloned into the aperture at page load. It must fill the stage the same way in both places, so give it position: absolute; inset: 0 inside the stage (the script sizes the cloned copy to the stage's pixel size for you). Give it its own background; a transparent content root shows the aperture's own --surface colour inside the glass where the page shows whatever is behind it.
  • The lens skeleton. The five elements in step 3 are structural. The script injects .lens-optics > .lens-view > .lens-layer copies into .lens-aperture; everything else about the lens (bezel look, glare colours, shadow) is CSS you are free to restyle.
  • The SVG filter ids are contractual. #lens-distortion, #lens-displacement-map, #lens-filter-red, #lens-filter-green and #lens-filter-blue are referenced from the stylesheet and the script; keep the ids if you move the block. The three colour layers reassemble the full image by screen-blending, so all three must stay present or the colours inside the glass drift.

What is only the demo's CSS and can be deleted: everything under the SPECIFICATION SHEET banner in style.css (the sheet, its tables, the drawing plate and the photographs), the .showcase-* shell/toolbar, and the glass-tint blocks keyed off data-lens-tint. The demo loads Mona Sans from Google Fonts; the effect does not depend on any face.

Photographs and SVG inside [data-lens-content] are cloned with everything else, so <img> tags work as they are. Serve them at roughly twice their rendered size (the demo bakes the bench at 1200px for a 450px column) or the loupe magnifies pixels rather than detail. Avoid id attributes on anything inside the content root: the clones would duplicate them.

Non-obvious CSS the effect depends on:

  • .lens-aperture needs overflow: hidden and the radial mask-image; without them the magnified copy escapes the glass.
  • .lens-view needs transform-origin: 0 0. The counter-translation maths assumes it.
  • .glass-lens is centred by negative margins of half --lens-size and positioned via GSAP transforms; do not add your own transform to it.
  • The lens is hidden without JavaScript via html:not(.has-js) .glass-lens. Keep that rule: the empty lens shell is meaningless without the script.
  • The cloned copies triple the DOM under the content root. Keep [data-lens-content] to hundreds of nodes, not thousands, and avoid videos or iframes inside it (they will not stay in sync).

Options

All read from the [data-lens-stage] element, all live: change the attribute at runtime and the lens follows (a MutationObserver watches them). The demo's toolbar is nothing more than buttons stamping these attributes.

Attribute Values Default Description
data-lens-zoom number 2.2 Magnification factor. The change is tweened.
data-lens-dispersion number 1 Multiplier on the chromatic fringing. 0 disables it entirely.
data-lens-filter invert or absent absent Film-negative view inside the glass only.
data-lens-focus on any focusable element – Keyboard hook: when the element receives focus, the loupe glides to it. Pair with tabindex="0".
data-lens-tint any string absent Not read by the script. A CSS hook for restyling the glass hardware per mode (see the tint blocks in style.css).

--lens-size in :root sets the physical size of the loupe (240px desktop, 170px under 900px in the demo).

Accessibility

  • Reduced motion: the physics loop, tilt, fringing and idle orbit are all disabled; the lens positions directly under the pointer and zoom changes are instant. The CSS also forces the chromatic layers off.
  • Keyboard: elements marked data-lens-focus (with tabindex="0" and a sensible aria-label) glide the loupe to themselves on focus, mirroring hover.
  • No JavaScript: the page renders normally without the lens; nothing is hidden behind the effect.
  • The lens itself and every cloned copy are aria-hidden, and focusables inside the clones are removed from the tab order.
  • Touch: see below — the loupe is pinned to the screen and lifted off the fingertip.
  • Attract orbit: a slow figure-8 runs on load to show the optics off, for a fixed number of passes, then parks back at the centre. Once the visitor has driven the lens themselves it does not run again. It is an attract loop, not an ambient animation.

Touch behaviour

A pointer-following loupe needs rethinking on a phone, and the effect handles it rather than leaving it to the buyer:

  • The glass is lifted off the contact point. A fingertip otherwise covers the magnified centre, which is the one part you are trying to read. Near the bottom edge, where there is no screen left to put a finger below the glass, the lift tapers off so the last of the board stays reachable; the loupe holds position when you let go, so it is still readable there.
  • The loupe is pinned to the viewport, and the page scrolls underneath it. touch-action is pan-y, so the browser claims vertical gestures for scrolling and the lens receives almost no pointermove on a downward drag. Left in page coordinates the loupe also scrolls away and, with no vertical drag available, cannot be brought back. Scrolling moves it down the board; dragging moves it across.
  • Tapping a card does not yank the loupe to that card. The cards carry tabindex, so a tap focuses one; the focus glide is a keyboard affordance and is gated on the last input modality so it does not fight the pointer.

Performance

Each colour channel is a full-size copy of the page behind its own SVG filter, and the two chromatic ones are screen-blended, so the channel count is the per-frame render budget. The effect picks it by pointer type:

  • Fine pointer: all three channels, the #lens-distortion turbulence, and the chromatic fringing.
  • Coarse pointer: one unfiltered channel. Magnification, tilt, the bezel glare and the elastic settle are unchanged; the fringing and the refraction wobble are dropped. Traced on a throttled iPhone 13 profile, that takes raster work from 9.8s per 5s of wall clock (a quarter of frames dropped) to 1.1s (frame budget held).

The loop is also gated on an IntersectionObserver and visibilitychange, so it stops entirely when the stage scrolls out of view or the tab is hidden.

If you want the full optics everywhere, change the channel list passed to buildOptics in script.js; the single-channel styling hangs off the .lens-optics--simple class it adds.

Dependencies

  • GSAP core 3.x (no plugins)

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.