苹果风格的液态玻璃用于 Web — 一个无头的 React 透镜,可在 Safari、F
Real liquid glass for the web. A headless React lens that refracts the live DOM, so the text stays selectable, the links stay clickable, and the motion is real. It renders in Chrome, Safari, and Firefox, with zero runtime dependencies. React is the only peer.
Live demo + docs → glass.samasante.com
Most "liquid glass" libraries use
backdrop-filter: url(), which only works in Chromium, so they fall back to a flat blur in Safari and Firefox. The WebGL ones rasterize anhtml2canvasscreenshot, so the text under the glass is frozen and stale. This one runs an SVG displacement filter on the element itself (filter: url()), so it refracts the real, live DOM, and it works across browsers.
npm i @samasante/liquid-glass
# react + react-dom are peer depsimport { Glass } from "@samasante/liquid-glass";
Save
;Wrap any styled box and it becomes a glass material: it frosts and tints the
page behind it, rims it with a soft bright edge, and — in Chrome / Edge —
bends the live page through it. Your translucent colour is the tint; the
children render crisp. No provider, no CSS import, no config. Size and style it
with CSS (className, style, Tailwind); change any optic through optics.
The one cross-browser catch (web-platform physics, not a choice). Bending the
live page uses backdrop-filter: url(), which ships in Chrome / Edge only.
In Safari and Firefox a wrapped `` still frosts + tints + edge-lights —
it reads as glass — but it can't bend the live page; bending there needs a copy
of what's behind the glass.
To bend in every browser, refract a copy — the same ``, you just tell it what to bend. It works in Chrome, Safari, and Firefox:
In-place — give a `` geometry (size + center) and it bends its own
children (a hero, a card), no copy to manage:
;refract={node} — float a lens over content it doesn't own (a panel over a photo,
a loupe over the page). Hand it the node to copy; behind fills the bleed; children
render crisp on top:
;src / draw + lenses — a /, refracted on the GPU (live
media an SVG filter can't reach). One renderer samples the medium and draws many
lenses over it, so each control of a video player is its own lens.
One rule across every mode: children are the crisp layer on top. See the
copy-paste GlassNotification
(panel over a photo) and GlassVideoControls
(lenses over a ``).
| this | other SVG-filter libs | WebGL snapshot libs | |
|---|---|---|---|
| Refracts live DOM (text selectable, links clickable) | yes | yes (Chrome only) | no, static screenshot |
| Works in Safari + Firefox | yes | no, flat blur | yes, but a snapshot |
| Headless / composable | yes | no, a styled widget | no |
| Bundle | tiny, 0 deps | small | large (three.js) |
A rounded-rect signed-distance field is rasterized to a displacement map (red
and green encode X/Y displacement, blue a specular mask). That map feeds an SVG
feDisplacementMap on the content via filter: url(#…), with a 3-pass RGB split
for chromatic aberration. Geometry is driven by lightweight motion values written
imperatively each frame, so a lens follows the pointer or animates at 60fps
without re-rendering React.
The hard part is Safari — the WebKit-specific fixes (1× filter, shape-only map
regeneration, cache-busting filter ids) are built in. See BROWSERS.md
for details and the support matrix.
| Prop | Type | Default | Notes |
|---|---|---|---|
children |
ReactNode |
none | The DOM the lens refracts (DOM mode). |
refract |
ReactNode |
none | Refract THIS instead (e.g. a sibling image/video). The children render crisp on top. |
behind |
string |
auto | Solid fill for the bleed edge of a refract copy. Omit it to auto-derive from the page's background; set it for a precise edge over a photo, or "transparent" to opt out. |
optics |
Partial |
balanced default | The look. See the list below. |
width, height |
number | motion |
fit the element | Lens size in full px. Omit and the lens fits the wrapped element. |
size |
number | [w, h] |
Shorthand for width + height (a number is square). |
|
radius |
number | motion |
the element's radius | Corner radius in px. |
center |
{ x, y } |
{ 0.5, 0.5 } |
Lens centre as a fraction (0 to 1) of the element — for a positioned or moving lens (a slider thumb, a video control). |
src, draw |
string, fn |
A video URL or a per-frame canvas painter (WebGL mode). | |
filterResolution |
number |
1 |
Chromium-only supersample (2 is crisp). Forced to 1 in Safari. |
live |
boolean |
false |
Re-rasterize every frame for self-animating refracted content (Safari). |
width, height, radius, and center.x / center.y take a plain
number or a motion value (anything with { get(); on('change', cb) },
including a framer-motion MotionValue), so a control can animate them at 60fps.
optics={{ … }})One GlassOptics vocabulary (the look, with no geometry) drives both the DOM
`` and the WebGL surface below.
strength: refraction strength (the most a pixel moves, a 0 to 1 fraction of
the box). scaleX and scaleY override it per axis.depth: how far the bend reaches in from the edge (0 to 1). It also gates
curvature.curvature: the convex dome (0 to 1), the magnified "liquid" middle. depth
gates it, so at a low depth the centre stays flat.dispersion: chromatic aberration, the colour split at the edges.bend: the rim refraction (0 to 1) — the "liquid" lip; extra inward refraction
in a thin band at the edge so the background wraps at the contour. 0 is a plain
magnifier.bendWidth: the width of that band, a 0 to 1 fraction of min(W, H). Default
0.16.sheen, sheenWidth, sheenFalloff, sheenAngle: the directional edge
highlight (intensity, thickness, falloff, and the angle it pools toward).
specular is the overall gain.glow, glowSpread, glowFalloff: the soft inner glow.frost: frosted blur. brightness: the veil. splay: corner splay.sheenDark and the *Shadow fields are DOM `` only. On the WebGL
surface, use CSS on the container instead.One balanced default look ships out of the box, with no presets to pick from.
Override any optic through optics: ``.
glassValue, animateGlassValue, deriveGlass, cubicBezier, glassEase,
useLensWobble (velocity squash-stretch), rubberBand, and GlassDiv (a
transform-only div). None of them are required to use ``.
Safari won't SVG-filter a live , so for media runs one WebGL
renderer that samples the medium and draws every lens from it. Pass src (a video)
or draw (a per-frame `` painter), and a full-px lenses={[{ x, y, w, h, radius }]} array (one optics, shared) for many lenses over one surface — each
control of a video player is its own lens bending the footage. The interactive
controls are the children, crisp on top.
import { Glass, type GlassSurfaceLens } from "@samasante/liquid-glass";
const lenses: GlassSurfaceLens[] = [
{ x: 0.27, y: 0.5, w: 62, h: 62, radius: 31 }, // rewind
{ x: 0.5, y: 0.5, w: 104, h: 104, radius: 52 }, // play / pause
{ x: 0.73, y: 0.5, w: 62, h: 62, radius: 31 }, // forward
];
{/* crisp transport controls, positioned over the lenses */}
;
// A generative canvas: pass `draw`; the lens refracts the painted frame.
paint(ctx, t)} size={160} center={pt} />;You drive playback yourself with videoRef + paused (plus poster / loop /
muted / autoPlay / crossOrigin) — a normal controlled component, no context.
GlassSurfaceLens types the lenses array. The full player is in the copy-paste
GlassVideoControls
example.
Finished components live in examples/
as code you own and restyle: a video player (GlassVideoControls), and
macOS-style switch, slider, notification, and context menu panels.
The glass part is tiny — a few props. The line count in the examples is the accessible, interactive shell (a real ``, drag, keyboard, ARIA), not the lens. Here's the glass-only essence of each:
// Switch — the lens IS the thumb; it slides 0→1 and bends the track through it.
// Slider — a gentler bend; the handle drives the lens, refracting the fill beneath.
// Notification / menu — a panel over a photo: it refracts a copy of the wallpaper;
// the crisp content + border sit on top (see the example for the full card).
} behind="#222" />Copy any file from examples/:
the engine ships on npm (versioned, you get fixes) while the components are
yours to own and restyle.
Chrome and Edge, Safari (including iOS), and Firefox. See BROWSERS.md.
An implementation of the SDF displacement-map glass technique that Apple popularized as "Liquid Glass", following the approach Aave's team documented in Building glass for the web. Thanks also to liquid-dom for another take on glass in the browser. Not affiliated with or endorsed by Apple.
MIT © Sam Asante
暂无开放 Issues,或尚未同步最近议题。