liquid-glass

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Liquid Glass

Liquid Glass

Reusable refraction glass for
components/grid-wallet-demo
. Full docs:
components/grid-wallet-demo/src/components/liquid-glass/README.md
— read it before implementing anything non-trivial.
可复用的折射玻璃,用于
components/grid-wallet-demo
。完整文档:
components/grid-wallet-demo/src/components/liquid-glass/README.md
—— 在实现任何非琐碎功能之前请先阅读。

The rule that prevents 90% of the pain

能避免 90% 痛苦的规则

The glass refracts its own CHILDREN, not the page behind it. There is no "frost whatever is behind me" mode (
backdrop-filter
can't refract portably). To bend a background you put a copy of it inside the glass. So:
  • Glass over a known backdrop (color / gradient / image / pattern) → use
    GlassOver
    , which makes the copy for you.
  • Glass over live UI behind a small surface (over a static screen) → achievable: feed the glass a positioned copy of that UI. See "Refract live UI behind a small surface" in the gotchas / README.
  • Glass over arbitrary scrolling/animating DOM → not achievable. Tell the user.
Exception: a big frosted surface (sheet/modal) should NOT refract — the per-frame SVG filter tanks Safari. Use
FrostPanel
(GPU
backdrop-filter
+ a specular edge). Refraction is for the small, tactile elements.
Skipping this gives a flat panel that doesn't lens (the classic failure).
玻璃折射的是它自己的子元素(CHILDREN),而不是其背后的页面。 不存在 “把我后面的东西磨砂”的模式(
backdrop-filter
无法可移植地折射)。要 弯曲背景,你需要把背景的副本放进玻璃内部。因此:
  • 玻璃放在已知背景(纯色 / 渐变 / 图片 / 图案)上 → 使用
    GlassOver
    ,它会为你生成副本。
  • 玻璃放在小表面背后的实时 UI 上(覆盖在静态屏幕上)→ 可实现:把该 UI 的一个定位副本喂给玻璃。参见 gotchas / README 中的 “折射小表面背后的实时 UI”。
  • 玻璃放在任意的滚动/动画 DOM 上 → 不可实现。请告知用户。
例外:大尺寸磨砂表面(sheet/modal)不应折射——逐帧 SVG 滤镜会拖垮 Safari。使用
FrostPanel
(GPU
backdrop-filter
+ 高光边缘)。折射用于小而 有触感的元素。
跳过这一原则会得到一个没有透镜效果的平面面板(经典失败)。

Pick the right entry point

选择合适的入口

NeedUse
Glass button / tab bar / card / chip over a known bg
GlassOver
(easiest)
Refract content you already render, or custom backdrop wiring
Glass
(=
LiquidGlass
)
A big frosted sheet/modal (no refraction — fast on Safari)
FrostPanel
(
BottomSheet
uses it)
A small surface that refracts the (static) screen behind it
GlassOver
's
backdropNode
A lens over the LIVE aurora that must work on SafariWebGL:
AuroraLensButton
(circle) /
AuroraLensPanel
(rect squircle)
The phone preview's WebGL stage
glass-gl/StageGL
— bespoke, don't reuse
SVG vs shader (which renderer): default to the SVG path (
Glass
/
GlassOver
) for anything made of live DOM — buttons, tab bars, cards, text, layouts (keeps content selectable/clickable). The shader / WebGL path is only needed when you're refracting content that is not live DOM — a
<canvas>
drawing (e.g. a generated QR) or a
<video>
(Safari won't SVG-filter those). There is no auto-switching and no general "glass over a canvas/video" yet: the only WebGL renderer (
StageGL
) is bespoke to the phone and re-derives the math in GLSL. If a canvas/video glass actually comes up, generalize
StageGL
(or build a map-fed WebGL glass) on demand
— don't assume a drop-in exists.
需求使用
已知背景上的玻璃按钮 / 标签栏 / 卡片 / chip
GlassOver
(最简单)
折射你已渲染的内容,或自定义背景接线
Glass
(即
LiquidGlass
)
大尺寸磨砂 sheet/modal(无折射,Safari 上很快)
FrostPanel
(
BottomSheet
使用它)
折射背后(静态)屏幕的小表面
GlassOver
的
backdropNode
必须兼容 Safari 的实时 aurora 透镜WebGL:
AuroraLensButton
(圆形)/
AuroraLensPanel
(矩形 squircle)
手机预览的 WebGL 舞台
glass-gl/StageGL
—— 专属定制,不要复用
SVG vs shader(选用哪个渲染器): 对于任何由实时 DOM 构成的内容——按钮、标签栏、卡片、文本、布局——默认走 SVG 路径(
Glass
/
GlassOver
),这样能保持内容可选中/可点击。shader / WebGL 路径只在折射不是实时 DOM 的内容时需要:
<canvas>
绘图(如生成的 QR)或
<video>
(Safari 不会对这些做 SVG 滤镜)。目前没有自动切换,也没有通用的“canvas/video 上的玻璃”方案:唯一的 WebGL 渲染器(
StageGL
)是手机专属定制,并在 GLSL 中重新推导数学。如果确实出现 canvas/video 玻璃需求,请按需泛化
StageGL
(或构建一个由 map 驱动的 WebGL 玻璃)
——不要假定已有现成方案。

Minimal usage

最小用法

tsx
import { GlassOver } from '@/components/liquid-glass';

const BG = 'linear-gradient(135deg, #5b9dff, #b07cff)';

<div style={{ background: BG }}>
  <GlassOver backdrop={BG} radius={16} depth={10} scale={18} cornerSmoothing={0.6}>
    <span style={{ display: 'block', padding: '12px 22px', color: '#fff', fontWeight: 600 }}>
      Settings
    </span>
  </GlassOver>
</div>
backdrop
= the same CSS background the surface sits on (gets refracted).
children
= content on top (not refracted). Any
GlassConfig
field is a prop. Start from a preset (
DEFAULT_GLASS
,
PHONE_SHELL_GLASS
) and override. Frosted surfaces (
FrostPanel
) use the slimmer
FrostConfig
, not
GlassConfig
.
tsx
import { GlassOver } from '@/components/liquid-glass';

const BG = 'linear-gradient(135deg, #5b9dff, #b07cff)';

<div style={{ background: BG }}>
  <GlassOver backdrop={BG} radius={16} depth={10} scale={18} cornerSmoothing={0.6}>
    <span style={{ display: 'block', padding: '12px 22px', color: '#fff', fontWeight: 600 }}>
      Settings
    </span>
  </GlassOver>
</div>
backdrop
= 表面所坐落的同一个 CSS 背景(会被折射)。
children
= 顶部内容(不会被折射)。任意
GlassConfig
字段都可以作为 prop。 从预设(
DEFAULT_GLASS
、
PHONE_SHELL_GLASS
)开始并覆盖。磨砂表面(
FrostPanel
) 使用更精简的
FrostConfig
,而不是
GlassConfig
。

Must-know gotchas (details in the README)

必知的坑(详见 README)

  • Hover/resize: animate
    transform: scale()
    , never width/height/radius — the displacement map re-bakes on shape change (jank).
  • Refraction needs texture: over a flat color you only see specular/edge rim.
  • Tiny elements: lower
    depth
    /
    scale
    or they over-distort.
  • specularRotation
    is directional (45° puts a bright highlight on the top-left/bottom-right corners) — rotate or lower
    edgeStrength
    to move it.
  • corner-shape: squircle
    is Chromium-only (graceful circular fallback).
  • Outer drop shadow → round the wrapper. A glass surface clips itself with
    overflow: hidden
    , so an outer
    box-shadow
    has to live on a wrapper around the glass (inside, the clip eats it).
    box-shadow
    traces that wrapper's own
    border-radius
    /
    corner-shape
    , not the glass child's — so an unrounded wrapper gives a square halo around round glass. Give the shadow wrapper the same
    border-radius
    +
    corner-shape
    as the glass (see
    GlassSymbolButton
    /
    GlassWindowButtonGroup
    .root
    ). Same rule for any border/background painted on a wrapper. Debug heuristic: a square around glass = a rounding-unaware layer.
  • Tiled backgrounds: pass
    GlassOver
    's
    backdropOffset={{x,y}}
    to align.
  • Circles & pills auto-lens radially (Aave's production circle/pill model, detected from the geometry — no flag).
    splay
    doesn't apply to them; the dome applies to circles only. Don't fight it with per-axis expectations.
  • Live/changing content inside a lens is a Safari minefield — stale filter-output caching, composited descendants escaping the lens unfiltered (
    translateZ(0)
    /
    will-change
    /motion's
    filter: blur(0px)
    leftovers), canvas/video killing the filter, and a LAYOUT-bounds source-size ceiling. Read "Safari field notes" in the README before attempting; the full experiment is parked on
    pat/scroll-refraction-experiment
    .
  • WebKit renders SVG displacement over a copied subtree as EMPTY output at notification size (confirmed June 2026 — see the README's "Confirmed dead end" postmortem). Don't retry it: use the WebGL lenses for Safari refraction and gate the SVG path to Chromium. Squircle-rect corner rules for Safari (map smoothing with
    cornerRadii
    , overlay border-radius intersection, one-element frost, no drop-shadow wrappers) are in the same section.
  • Verifying a bend over a soft backdrop: displacement is invisible without texture — draw a sharp debug grid through the bent coordinates to check.
  • Big sheets/modals don't refract — they frost. A large displacement lens re-runs its whole SVG filter every frame while the surface animates, which tanks Safari (CPU
    feDisplacementMap
    ). Use
    FrostPanel
    (GPU
    backdrop-filter
    + a static specular edge); keep refractive
    Glass
    /
    GlassOver
    for the small buttons on top. Example:
    apps/shared/BottomSheet
    (FrostPanel) →
    aurora/PasskeySheet
    .
  • Refract live UI behind a small surface: pass a copy of the behind-UI as
    GlassOver
    's
    backdropNode
    , anchored to align, and counter-animate it against any slide so it stays put while the glass slides over it. Behind-UI must be static while open. (Not for big sheets — see above.)
  • 悬停/缩放: 应使用
    transform: scale()
    做动画,绝不要 animate width/height/radius——形状变化会导致位移图重新烘焙(卡顿)。
  • 折射需要纹理: 在纯色背景上你只会看到高光/边缘轮廓。
  • 微小元素: 调低
    depth
    /
    scale
    ,否则会过度扭曲。
  • specularRotation
    是有方向性的(45° 会在左上/右下角产生明亮高光)——旋转或降低
    edgeStrength
    来移动它。
  • corner-shape: squircle
    仅 Chromium 支持(优雅回退为圆形)。
  • 外部投影 → 圆化 wrapper。 玻璃表面会用
    overflow: hidden
    裁剪自身,因此外部
    box-shadow
    必须放在玻璃外部的 wrapper 上(放在内部会被裁剪吞掉)。
    box-shadow
    会跟随 wrapper 自身 的
    border-radius
    /
    corner-shape
    ,而不是玻璃子元素——所以未圆角的 wrapper 会在圆玻璃周围形成方形光晕。给阴影 wrapper 设置与玻璃相同的
    border-radius
    +
    corner-shape
    (参见
    GlassSymbolButton
    /
    GlassWindowButtonGroup
    .root
    )。绘制在 wrapper 上的任何边框/背景同理。调试启发式:玻璃周围出现方形 = 某层不了解圆角。
  • 平铺背景: 传入
    GlassOver
    的
    backdropOffset={{x,y}}
    来对齐。
  • 圆形和胶囊形会自动径向透镜化(Aave 生产环境中的 circle/pill 模型,从几何形状自动检测——无需 flag)。
    splay
    不适用于它们;dome(穹顶效果)仅适用于圆形。不要用逐轴预期去对抗它。
  • 透镜内的实时/变化内容是 Safari 雷区 —— 陈旧滤镜输出缓存、合成后代未经过滤地逃出透镜(
    translateZ(0)
    /
    will-change
    /motion 遗留的
    filter: blur(0px)
    )、canvas/video 杀掉滤镜,以及 LAYOUT-bounds 源大小上限。在尝试之前请阅读 README 中的“Safari 现场笔记”;完整实验停在
    pat/scroll-refraction-experiment
    分支。
  • WebKit 会将复制子树上的 SVG displacement 渲染为空输出(通知尺寸下;2026 年 6 月确认——见 README 的“已确认死路”事后分析)。不要重试:Safari 折射使用 WebGL 透镜,并将 SVG 路径限制在 Chromium。同一节还包含 Safari 的 squircle-rect 圆角规则(用
    cornerRadii
    做 map 平滑、overlay border-radius 交叉、单元素 frost、不用 drop-shadow wrapper)。
  • 验证柔和背景上的弯曲: 没有纹理时位移不可见——用尖锐的调试网格穿过弯曲坐标来检查。
  • 大尺寸 sheet/modal 不折射——它们磨砂。 大型位移透镜在表面动画时每帧重跑整个 SVG 滤镜,拖垮 Safari(CPU
    feDisplacementMap
    )。使用
    FrostPanel
    (GPU
    backdrop-filter
    + 静态高光边缘);把折射
    Glass
    /
    GlassOver
    留给上面的小按钮。示例:
    apps/shared/BottomSheet
    (FrostPanel)→
    aurora/PasskeySheet
    。
  • 折射小表面背后的实时 UI: 将背后 UI 的副本作为
    GlassOver
    的
    backdropNode
    传入,锚定对齐,并反向动画它,使玻璃在滑动经过时它保持不动。打开期间背后 UI 必须是静态的。(不适用于大 sheet——见上文。)

When tuning

调试时

There's a live tuning panel: run the demo (
npm run dev
, port 4000), open the App panel, switch it to swag mode. Sliders map 1:1 to
GlassConfig
.
有一个实时调试面板:运行 demo(
npm run dev
,端口 4000),打开 App 面板,切换到 swag 模式。滑块与
GlassConfig
一一对应。