模糊工艺 (Blur craft)
大多数网络上的玻璃效果看起来很廉价,原因有三:背后没有什么有趣的东西,材质只有一层平坦的着色和一层模糊,以及上面的文字是没人测量过的混浊灰色。看起来昂贵的玻璃(苹果的材质、visionOS、最好的产品网站)按顺序做对了三件事:
- 玻璃背后的光需要精心设计。 玻璃会展示背后的东西,所以请在那里打光。
- 玻璃是一种材质,而不是一种着色。 饱和模糊、渐变填充、发光边缘、镜面光泽、深度、颗粒感。这就是
assets/glass.css。 - 文本需要被测量。 半透明玻璃会随着背后每个像素的变化而改变对比度,因此需要在最差的点上通过脚本、在三种状态下检查对比度(
scripts/glass-contrast.mjs)。
快速路径
| 我正在构建… | 使用 | 备注 |
|---|---|---|
| 粘性导航栏 | .gs-bar (surfaces.css) | 扩展背景;添加 gs-bar--on-scroll 使其仅在滚动后才变得玻璃化 |
| 覆盖在艺术图上的播放器、底座或药丸按钮 | .gs-dock.glass.glass--clear + liquid-glass.js | 清晰的液体玻璃,每屏一个;如果艺术图变化很大,则自适应色调 |
| 带文本的浮动卡片或面板 | .glass.glass--regular | 在细节丰富的照片上使用 thick |
| 菜单、弹出菜单或命令栏 | .gs-menu.glass | 32px 覆盖约 86%,0.5px 顶部高亮 |
| 对话框 | .gs-veil + .gs-dialog.glass.glass--thick | 确认付款、删除或签名时使用 glass--solid;当玻璃镀铬层位于下方时使用 gs-veil--flat + .gs-modal-open |
| 浮动提示或底部弹窗 | .gs-toast.glass.glass--regular / .gs-sheet.glass.glass--thick | gs- 类仅用于布局;材质来自 .glass |
| 内容在栏下淡出 | 渐进模糊(配方 2) | 每个视图一个滚动边缘效果 |
| 标题出现或数值变化 | 模糊揭示(4)/ 2px 交叉淡入(4b) | 每屏一次 |
| 页面被艺术图的颜色浸染 | 艺术背景,烘焙(配方 8) | 绝不在固定玻璃下使用实时模糊 |
| Tailwind v4 项目 | glass.css + glass.tailwind.css (glass-blur-24, …) | Tailwind 自身的 backdrop-blur-* 在 Sonoma Safari 上会失效 |
| 原生应用 (SwiftUI, Flutter, React Native, Compose) | references/native.md | 相同方法,每个工具包各自的材质 |
| 照片、视频或设计编辑器的镀铬层 | .glass.glass--thick.glass--neutral | 中性:不增加饱和度,玻璃不能改变作品的颜色 |
然后:设计光(§1),设置材质(§2),检查(§5)。assets/demo.html 实时展示了每个表面并带控制选项;打开它可以在选择之前看到每个类的效果。
1. 先设计光
确定每个玻璃表面正后方是什么,并让它成为那里最发光的东西。
-
在玻璃背后放置一个光源: 一个光晕、艺术图中的太阳、产品的颜色、一个实时的 WebGL 组件。模糊的纯色仍然是纯色;虚空之上的玻璃只是一个灰色盒子。
-
让光穿过边缘。 最好的玻璃一半覆盖在光上,一半覆盖在暗处,这样颜色会沿着它的宽度变化。
-
将边缘朝向光源。 将
--glass-light设置为光的颜色(日落时为255 200 160),将--glass-rim-angle设置为它的方向。边缘的亮端面向光源:光源来自 左上 上 右上 右 右下 下 左下 左 角度 135deg 180deg 225deg 270deg 315deg 0deg 45deg 90deg 位于玻璃下方的光源,其角度取最接近边缘的角度加上约 15deg 朝向中心。该角度是一个注册的
@property,因此它可以跟随指针或滚动平滑移动(苹果的高光会随着光移动而在材质周围移动)。 -
将玻璃的尺寸做得像主角。 当它足够大以显示其下的场景变化时,它才会被解读为一种材质。苹果使更大的玻璃表现为更厚的玻璃:更深的阴影,更柔和的散射。
2. 设置材质
使用 assets/glass.css(复制时重命名 glass 前缀)和 assets/surfaces.css 来获得现成的表面,或者使用组件:Glass.tsx (React)、Glass.vue (Vue 3)、Glass.svelte (Svelte 5)。不要手写一行的 backdrop-filter 卡片。
使用字面量值编写 backdrop-filter,绝不用 var(),无前缀的声明放在最前,-webkit- 前缀的放在最后。 macOS Sonoma 上的 Safari 18 和 26 在使用自定义属性构建滤镜时不渲染模糊效果(WebKit 问题 297620,未修复),而 Tailwind v4 的 backdrop-blur-* 正是这样构建的。同一问题报告显示,即使使用字面量值,那里的无前缀属性也会失效,因此带前缀的声明放在最后,Safari 会应用它(Chrome 和 Firefox 会忽略它)。glass.css 为每种厚度 × 色调提供了一个字面量滤镜,并通过类切换;变量适用于填充、尺寸和颜色。
厚度。 根据玻璃承载的文本量来选择。数值通过 demo.html 中的对比度检查器在明亮光源下进行校准:
| 类 | 模糊, 暗化 | 在明亮光源下通过 4.5:1? | 用途 |
|---|---|---|---|
glass--clear | 16px, 22% | 否:仅用于粗体或大号文本及图标 | 底座、播放按钮、覆盖在媒体上的药丸按钮(苹果:清晰的玻璃需要粗体、明亮的内容和一个暗化层) |
glass--thin | 24px, 30% | 在极亮光源下不通过 | 控件、芯片、小卡片在中等背景下 |
glass--regular | 32px, 48% | 在校准页面上通过;播放器背后明亮的日落需要 thick 设置为 0.62 | 播放器、面板、浮动提示:默认值。仅着色(模糊关闭)状态通常是第一个失败的 |
glass--thick | 44px, 56% | 是 | 菜单、弹窗、对话框、任何文本密集的内容 |
glass--solid | 4px, 92% | 是 | 支付、删除、签名(高于 ~85% 填充时大模糊无法被察觉,因此使用 4px) |
添加 glass--light 用于浅色玻璃搭配墨水色文本,以及 glass--neutral 用于颜色关键型应用(照片、视频和设计编辑器):不增加饱和度或色调偏移,因为玻璃不能改变作品的外观。在细节丰富的照片上,从 thick 开始,使用 --glass-dim: rgb(10 10 18 / 0.65–0.75)(它是一个颜色,不是一个数字):生动的玻璃和可读的文本在此处相互冲突,因此将生动的部分用在边缘、光泽和玻璃周围溢出的光上。
两个层级,如同 iOS 26.1: 一个着色层级(更多不透明度,绝不更少)会在 prefers-contrast: more(Safari 用户可以访问的设置;Safari 没有 prefers-reduced-transparency)时启用,使用 glass--tinted,或通过 <html data-glass="tinted"> 全站启用。glass.css 也处理 Windows 高对比度模式(纯色 Canvas 表面)、打印(带边框的普通盒子)、屏幕截图工具的捕获模式,以及 RTL。
玻璃属于控制层(导航栏、工具栏、底座、菜单、弹窗、对话框)浮动在内容之上,从不属于内容本身,也绝不做玻璃叠玻璃(苹果,WWDC25)。仅对主要操作进行着色。匹配简报的基调:身临其境的简报采用强光和在主角控件上的清晰玻璃;信任型(金融、健康)则采用厚或实心玻璃,几乎不发光。
references/material.md 包含了其余内容:每个层的作用及原因、从 Apple.com、Linear、Raycast、Radix、Material 和 Fluent 读取的预设、苹果完整的 WWDC25 规则列表,以及基调表。
3. 标志性效果
references/recipes.md 中的代码:
- 液体玻璃折射: 边缘像透镜一样弯曲其背后的东西,可选色差(0.6–1)。两个版本:
liquid-glass.ts/.js可以折射任何页面内容,但仅在 Chromium 中有效(Safari 不会为 SVG 背景滤镜绘制任何东西,WebKit 问题 245510);liquid-glass-webgl.js折射页面自身绘制的背景(一个<canvas>、<img>或<video>,比如一张英雄壁纸),在每个浏览器中都有效,已在 Chromium 和 WebKit 中验证渲染。在其他地方,磨砂材质必须看起来自成一格。折射和磨砂之间存在权衡:弯曲效果在清晰玻璃上可见(blur0–6);磨砂效果(12–20)覆盖整个边缘到边缘,弯曲变成柔和的色彩偏移。清晰玻璃用于一个词或一个图标,磨砂玻璃用于句子,文本距离边缘一个边框宽度。绝不要在覆盖遮罩层的对话框上使用:那里已经没有清晰的东西可以弯曲了。 - 粘性栏的扩展背景: 颜色在内容滚动到玻璃下方之前就进入玻璃。
gs-bar--on-scroll使栏在滚动后才变得玻璃化,仅使用 CSS(滚动驱动动画)。 - 渐进模糊: 内容在栏下溶解而不是被剪切。堆叠的遮罩层是唯一的方法,直到 CSS 拥有
linear-blur()(已提议,csswg #13285)。 - 模糊桥接交叉淡入: 状态切换时 2px 模糊(Emil Kowalski)。
- 模糊揭示: 从
blur(8px)开始,一次性,作为出现时刻。 - 焦点遮罩: 对话框背后的页面后退 4–8px。
- 玻璃形态与按压: 在附近玻璃形状之间的
layoutId形态(光标:0.34s,[0.32, 0.72, 0, 1])和按压时的内发光脉冲,不是缩放。 - 合并玻璃形状: 一个被 goo 滤镜 SVG 遮罩切割的玻璃层,使附近形状像苹果的玻璃容器一样用液态颈部连接(配方 11,已在 Chromium 中测试)。
- 视频的环境光晕: 一个 10×6 的画布样本,取自正在播放的视频,放大(配方 8b)。
- 着色: 结构玻璃的色度约 0.03–0.05,着色的相对颜色 alpha(配方 12)。
- 景深、速度运动模糊、艺术背景(烘焙)、锁定内容、颗粒感。
4. 保持快速
先设计外观,然后坚持以下几点:
- 长时间或循环动画作用于不透明度和变换,而不是模糊半径。短暂的一次性动画没问题:Apple.com 将其菜单帷幕的模糊从 0 动画到 20px,持续 320ms。
- 绝不同时模糊两个叠加的表面(苹果的导航栏在弹出菜单打开时会降低自身的模糊)。当对话框在玻璃镀铬层上打开时,向
<html>添加.gs-modal-open(镀铬层停止模糊并变为接近不透明)并使用gs-veil--flat,或者从图像或画布背景烘焙遮罩层。在照片编辑器测试中测量:一个实时的全屏遮罩层模糊覆盖五个玻璃面板,运行在 31fps;没有它,60fps。 - 固定或粘性模糊每个滚动帧都会重新运行。每屏只用一个大的;在其下烘焙大尺寸模糊图像(测量:实时模糊 60 → 22fps,烘焙后回到 60fps)。
- 手机:七个 18px 面板在 Mali-G57 上每帧约花费 3.5ms(75–80% 的帧按时完成 vs 没有时的 99%)。当面板超过几个时,在布局的手机断点处丢弃
backdrop-filter并保留半透明填充(或者让 glass-tier.js 根据每台设备决定)。 - 预算模糊区域,而不是半径或数量。 为此技能测量(Chromium,软件渲染 2×,移动背景,vsync 开启):半径 4 → 64px 和 1 → 9 个表面在相同总面积下没有区别;模糊屏幕的 90% 导致帧率降至 51fps(p95 33ms)。这与 Skia 下采样大模糊的行为匹配。数值是相对的(在快速的 Mac 上软件渲染),排序是通用的。
- 给
html一个不透明的背景色:这样 Safari 会跳过含玻璃页面的额外全屏合成层(WebKit 问题 271340:修复仅对不透明文档跳过该层)。 - 不要在不透明填充下模糊。Framer 和 Dia 在纯色背景上提供了
backdrop-filter,支付了无人能看到的模糊成本。在 ~85% 填充以上,使用 ≤ 4px 或没有(Arc 的浮动提示:85% 和 3px)。 - 按设备对玻璃效果分层:
assets/glass-tier.js根据设备内存、Save-Data、帧率探测(iOS 低电量模式故意将动画帧率限制在 30fps)和 Compute Pressure 设置data-glass-tier为全量、精简(一个 8px 模糊)或平坦(无模糊,更多着色),与 Android、Fluent 和 Apple 在弱电或节能设备上降低模糊的方式相同。 - 在视频上,不要模糊正在播放的视频:将其采样到一个小画布中(配方 8b),并在全屏时放弃玻璃效果。Safari 绝不会将视频像素传递给 SVG 滤镜。
- 背景根:一个父元素具有
opacity < 1、filter、遮罩、clip-path、mix-blend-mode、backdrop-filter或上述任何属性的will-change会压平其内的玻璃。这包括 Framer Motion 父元素淡入和玻璃斑点父元素上的 goofilter。自行淡入玻璃;在完成后清除揭示滤镜(filter: none)。 backdrop-filter使元素成为position: fixed子元素的包含块:将门户菜单、浮动提示和对话框移出玻璃容器。- 祖先上持续的
view-transition-name会在 Chromium 中压平玻璃;仅在过渡期间设置名称。包装器上的框架淡入过渡也会产生相同效果:自行淡入玻璃本身。 - 拍摄玻璃的屏幕截图、OG 图像或 PDF:html2canvas、Satori 和打印会丢弃或破坏模糊。在捕获期间在
<html>上设置data-glass-capture(glass.css 会使玻璃变为不透明),并查看 quirks.md。
5. 检查(必需)
在声明玻璃效果完成之前,运行所有这些检查:
- 霜冻检查:
node <skill>/scripts/frost-check.mjs <url or file.html>测量每个玻璃表面模糊移除的细节量(模糊开启 vs 关闭)。NO BLUR 意味着背景根、前缀错误或使用了var(),或 Liquid Glass 映射过期;FLAT 意味着背后没有任何东西可以测量。若要手动检查,请将粗体红色文本放在玻璃背后,在deviceScaleFactor: 1下捕获并放大。 - 对比度,在最差背景下测量:
node <skill>/scripts/glass-contrast.mjs <url or file.html>。请指向最明亮的现实状态,而不是漂亮的默认状态:最亮的照片、日光帧、推高 +2 EV 的图像、滚动到栏下的白色页面部分(添加 URL 参数或测试状态,以便脚本可以加载)。在一个编辑器中,默认照片以 8.7:1 通过,而一个明亮帧以 4.4:1 失败。也请检查手机宽度(--width 390 --height 844)。它会隐藏文本,找到每行文本下最亮和最暗的像素,并检查 4.5:1(大文本 3:1)在三种状态下:正常、仅着色(模糊关闭,如同 iOS 低电量模式显示的情况)和prefers-contrast: more。通过增加一个厚度层级、提高--glass-dim中的 alpha 值(例如
…(原文截断)