给网站换上毛玻璃效果

2026-07-01 前端

2026-10-03 更新:本文改用 Filter Effects Level 2 规范、CSS Color 4/5 规范、MDN 官方文档与 WCAG 2.2 官方说明重写,删去原先的本机实测数值;示例都是可直接复制的代码,并标明所依据的规范条款。

"毛玻璃"在 Web 上就是一个 CSS 属性:backdrop-filter。但真把它铺到全站之后,常见的反馈是"有的地方生效、有的地方没反应""字看不清了"。原因几乎都出在规范的几条硬性条件上——背景根本没有内容可模糊、或者祖先元素悄悄变成了"背景根"。本文按规范把条件和坑讲清楚,并给出可直接复制的示例。

一、真玻璃只有三件事

按规范,backdrop-filter 要生效必须同时满足:

  1. 元素本身有半透明的部分。Filter Effects Level 2 规范 §2 的原文说得很直接:The effect of the backdrop-filter will not be visible unless some portion of element B is semi-transparent.(除非元素某个部分是半透明的,否则看不出效果。)MDN 的表述是同一件事:因为它作用于元素背后的一切,要看到效果,元素或它的背景需要透明或部分透明。规范还补了一句:要创建"完整透出过滤后背景"的透明元素,用 background-color: transparent。
  2. 背后确实有内容。元素背后若只有纯色页面背景,模糊出来的还是同一片颜色,看不出差别。
  3. 没被祖先截断(下一节)。

属性取值为 none 或一个滤镜函数列表(blur()、brightness()、saturate() 等,也可以是 SVG 滤镜 url());初始值 none,不继承。

.glass {
  background-color: rgb(13 17 23 / 35%);   /* ← 半透明底,缺了这行就没有效果 */
  backdrop-filter: blur(10px) saturate(140%);
}

二、Backdrop Root:为什么"写了却不生效"

规范 §3 定义了 Backdrop Root(背景根):元素的 backdrop 图像是"从最近的祖先 Backdrop Root 起,按绘制顺序绘制到该元素为止的全部内容"展平成的 2D 缓冲。这意味着,模糊范围被最近的 Backdrop Root 截断。

规范 §3.2 列出了会形成 Backdrop Root 的情况:

  • 文档根元素;
  • filter 非 none;
  • opacity 小于 1;
  • mask / mask-image / mask-border / clip-path 非 none;
  • backdrop-filter 非 none(即毛玻璃元素自己也会形成背景根,这就是"嵌套毛玻璃"退化的原因);
  • mix-blend-mode 非 normal;
  • will-change 指定了上述任一属性。

MDN 给了一个典型的踩坑场景:父元素写了 opacity: 0.9,它就变成 backdrop root,子元素的 backdrop-filter 只会模糊"父元素与子元素之间"的内容——看起来像"没生效",其实是范围被截断了。

也有一个好消息:规范说明Backdrop Root 不会因为 z-index、fixed/sticky 定位或 transform 而形成(覆盖面比 stacking context 小),所以用容器做 transform 动画不会阻断子元素看到容器背后的背景。

三、连着来的两个副作用

规范 §2 的注释里说明:backdrop-filter 计算值不是 none 时,会创建 stacking context,并为 absolute/fixed 定位的后代创建 containing block(根元素除外),行为与 filter 属性一致。这意味着:

  • 内部用 position: fixed 弹出的子层,会以这个毛玻璃元素为参照,而不是视口——常见的"弹出层位置跑偏"就源于此;
  • 如果本来依赖 z-index 的层级关系,加上毛玻璃后层级会重新按新的 stacking context 计算。

四、性能:规范怎么说

规范 §3.1 的动机一节解释了为什么要有 Backdrop Root 这个约束:如果 backdrop-filter 过滤"之前绘制的一切",每次都必须单独渲染一遍,会翻倍所需渲染时间,并可能需要两倍内存与 GPU 带宽;嵌套则指数级恶化。§3.2 的总结是:对所有触发 Backdrop Root 的元素,放松这个约束都会带来 CPU/GPU 内存与带宽的潜在翻倍。

⚠️ 需要明确的边界:规范与本节的官方文档都没有给出"一个页面能放几个 backdrop-filter"的数值阈值。任何具体数字都属于站点自测结论,不是规范结论。可操作的判断依据是"每个毛玻璃元素都会让浏览器多渲染一份背后的内容",所以固定不动的页面级表面(导航、侧栏、底栏)比列表项里的毛玻璃划算得多。

五、示例 1:最小的毛玻璃面板(带回退)

思路:默认给接近实色的底保证可读性,只有在浏览器确实支持 backdrop-filter 时才把底变透明并开启模糊;-webkit- 前缀给旧 Safari。

/* glass.css —— 外部样式表,只用 class,不含内联样式或 style 属性 */
.glass-panel {
  /* 回退:不支持时用接近实色的底,保证文字对比度 */
  background-color: rgb(13 17 23 / 92%);
  color: #f0f6fc;
  border-radius: 12px;
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .glass-panel {
    background-color: rgb(13 17 23 / 35%);       /* 半透明,效果才可见 */
    -webkit-backdrop-filter: blur(10px) saturate(140%);
    backdrop-filter: blur(10px) saturate(140%);
  }
}
<div class="glass-panel">面板内容</div>

@supports 是 MDN 所说的"特性查询":用 property: value 形式检测浏览器是否支持某个声明,也可以用 and / or / not 组合。

六、示例 2:用一个数字调 alpha

透明度表达在 CSS Color 4 规范里是有定义的:<alpha-value> 可以是数字或百分比,省略时默认 100%,超出 [0,1] 的值在解析时被钳制而不是报错;rgb() 的现代语法允许斜杠分隔 alpha(rgb(R G B / <alpha-value>)),百分比里 0% 即 0.0、100% 即 1.0。

于是"调透明度"就是调一个变量:

/* toolbar.css —— 外部样式表,class 驱动 */
:root {
  --glass-alpha: .28;    /* 唯一旋钮:改这个数字即可 */
}

.glass-toolbar {
  background-color: rgb(13 17 23 / 92%);   /* 回退 */
  color: #f0f6fc;
}

@supports (backdrop-filter: blur(1px)) {
  .glass-toolbar {
    background-color: rgb(13 17 23 / var(--glass-alpha));
    backdrop-filter: blur(8px) saturate(140%);
  }
}

如果底色的"基色"是一个不透明值,也可以用 CSS Color 5 定义的 color-mix() 算出来:

.glass-card {
  background-color: rgb(13 17 23 / 92%);   /* 回退 */
}

@supports (background-color: color-mix(in srgb, red 50%, transparent)) {
  .glass-card {
    /* 基色占 35%,其余混入 transparent */
    background-color: color-mix(in srgb, #0d1117 35%, transparent);
  }
}

color-mix() 的官方定义是:接受一组 <color> 并按给定色彩空间与比例混合;若总百分比小于 100%,结果颜色会被乘以 p1+p2 的 alpha——这正是"从一个不透明基色算出半透明表面"的规范依据。MDN 记载它自 2023 年 5 月起为 Baseline Widely available,但"多个颜色参数"的形式支持尚不广泛,示例只用两色形式。

七、示例 3:尊重系统的"降低透明度"

Media Queries Level 5 定义了 prefers-reduced-transparency,用于探测用户是否要求系统减少透明/半透明层次效果;取值 no-preference / reduce。MDN 标注它目前仍是 Experimental 且非 Baseline(Firefox、Safari 不支持),所以只能当增强回退,不能当作唯一回退手段。

@media (prefers-reduced-transparency: reduce) {
  .glass-toolbar { background-color: rgb(13 17 23 / 96%); }
  .glass-panel   { background-color: rgb(13 17 23 / 96%); }
}

八、代价:最先扛不住的是文字

半透明底意味着文字背后的颜色是不确定的,而对比度是可访问性里被官方量化过的指标。WCAG 2.2 的相关条文(W3C WAI 官方说明):

  • SC 1.4.3 Contrast (Minimum),AA:正文文本与文本图像对比度至少 4.5:1;大号文本(约 18pt,或 14pt 粗体,约等于 24px / 18.5px)至少 3:1。官方同时说明:阈值不四舍五入(4.499:1 不达标),并且要按样式表给出的前景/背景色评估,而不是屏幕上抗锯齿之后的观感。
  • SC 1.4.11 Non-text Contrast,AA:用于识别 UI 组件与状态、以及理解内容所必需的图形,与相邻颜色的对比度至少 3:1。
  • SC 2.4.11 / 2.4.12 Focus Not Obscured:键盘聚焦的组件不得被作者内容完全遮挡;官方特别指出半透明叠层或带模糊/变暗效果的叠层也是遮挡来源,2.4.12(AAA)更进一步——即使聚焦组件仍部分可见,这种叠层也构成失败,并可能连带导致 1.4.11 与 2.4.13 失败。
  • SC 2.4.13 Focus Appearance,AAA:焦点指示器要满足面积要求,且聚焦/未聚焦状态下同一像素的对比度至少 3:1。

落到实践上就是三条:毛玻璃面板上的文字要按"最坏情况"(背后是最亮的内容时)核算对比度;固定导航这类会盖住内容的表面,给足不透明度或加一层文字阴影;页面级固定毛玻璃要确认它不会盖住键盘焦点框。

九、跨浏览器要记住的两个事实

MDN 的浏览器兼容数据(browser-compat-data)给出了具体版本:

  • Safari:无前缀名称自 Safari 18 起支持,而 -webkit-backdrop-filter 自 Safari 9 起就有——所以旧 Safari 必须带前缀。
  • Edge:-webkit- 前缀自 17 起、到 79 移除;无前缀自 79 起。
  • Firefox:103 起支持,但在 Firefox 123 之前,GPU 厂商未知的系统上不支持(官方 bug 1867937 记录)。这类环境会命中你的回退分支——这正是回退必须"可读"而不是"糊成一片"的原因。

十、落地清单

  • [ ] 每个毛玻璃表面都有半透明底(否则规范直接判定看不出效果);
  • [ ] 检查祖先有没有 opacity < 1、filter、mask、clip-path、mix-blend-mode——它们会让毛玻璃"看起来没生效";
  • [ ] 默认提供不透明的可读底色,@supports 通过后才降 alpha(顺序不能反);
  • [ ] 旧 Safari 带 -webkit- 前缀;
  • [ ] 大量列表项不要逐项加毛玻璃(规范说明每个都会多渲染一份背景),优先用在固定表面;
  • [ ] 面板上的文字按最坏背景核算 WCAG 对比度(4.5:1 / 大文本 3:1);
  • [ ] 页面级固定毛玻璃不要遮住键盘焦点框(SC 2.4.11/2.4.12);
  • [ ] 若用 color-mix() 或 prefers-reduced-transparency,都放进 @supports / @media 里做渐进增强。

参考来源