简体中文
CSS 实现渐变毛玻璃
CSS 实现渐变毛玻璃:从
backdrop-filter到多层mask
在很多现代网页中,底部工具栏、悬浮导航、弹窗和卡片并不是一块完全不透明的背景。它们会保留后方内容的颜色和轮廓,同时把细节柔化掉,看起来像一块半透明的磨砂玻璃。
普通毛玻璃的模糊强度是均匀的,整个区域看起来像盖了一层相同厚度的雾。渐变毛玻璃则不同:模糊会沿着某个方向逐渐增强或减弱,边界更柔和,也更适合做页面底部的过渡遮罩。
例如,一个固定在页面底部的渐变毛玻璃栏,可以让靠近内容区的位置仍然比较清晰,越接近底部越模糊。这样既能让内容自然地过渡到工具栏,又不会突然出现一条明显的白色分界线。
常见使用场景包括:
- 页面底部固定操作栏、返回按钮或聊天输入区;
- 顶部导航和悬浮工具栏;
- 图片或渐变背景上的浮层卡片;
- 需要弱化背景、但又不希望完全遮挡背景的弹窗;
- 滚动内容与固定区域之间的视觉过渡。
这篇文章只讨论渐变毛玻璃,不讨论完整的液态玻璃材质。核心问题也很明确:如何让 backdrop-filter 的模糊效果沿一个方向平滑变化?
普通毛玻璃为什么不够
最常见的毛玻璃写法是:
.glass {
background: rgb(255 255 255 / 20%);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}2
3
4
5
backdrop-filter 会处理元素背后的内容。blur(12px) 表示整个元素区域使用同一个模糊半径,因此它得到的是均匀的毛玻璃。
这种效果适合面积较小的卡片、弹窗和按钮。但如果它被用在一个高度较大的底部区域,就容易出现两个问题:
- 玻璃区域的顶部和底部模糊程度完全一样,层次感比较弱;
- 玻璃区域和页面内容之间像是硬切了一刀,过渡不够自然。
直觉上,我们可能会尝试这样写:
.glass {
backdrop-filter: blur(0) blur(12px);
}2
3
但这并不会产生从 0px 到 12px 的渐变。blur() 是滤镜函数,不是可以直接做插值的背景属性;同一个元素上的多个 blur() 也不是“前面小、后面大”的空间渐变。
要实现渐变毛玻璃,需要把问题拆成两部分:
- 用不同元素提供不同强度的模糊;
- 用蒙版控制每个模糊层在哪些位置显示。
渐变毛玻璃的基本原理
一个简单的渐变毛玻璃可以拆成下面几层:
背景内容
↓
轻度模糊层:覆盖范围最大
↓
中度模糊层:覆盖范围变小
↓
重度模糊层:只覆盖目标区域
↓
内容和交互层2
3
4
5
6
7
8
9
每个模糊层都使用 backdrop-filter,但模糊半径不同。例如:
.layer-1 {
backdrop-filter: blur(1px);
}
.layer-2 {
backdrop-filter: blur(3px);
}
.layer-3 {
backdrop-filter: blur(5px);
}
.layer-4 {
backdrop-filter: blur(7px);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
如果四层都完整显示,效果会变得过于厚重。于是再给每层加一个垂直渐变蒙版:
.layer-1 {
mask: linear-gradient(to bottom, #000 0 85%, transparent 100%);
}
.layer-2 {
mask: linear-gradient(to bottom, #000 0 61%, transparent 81%);
}
.layer-3 {
mask: linear-gradient(to bottom, #000 0 36%, transparent 56%);
}
.layer-4 {
mask: linear-gradient(to bottom, #000 0 12%, transparent 32%);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
这样一来,轻度模糊覆盖更大的范围,重度模糊只在局部出现,最终叠加出逐渐增强的模糊效果。
这里的 #000 不是黑色背景,而是蒙版中的“完全显示”;transparent 表示“完全隐藏”。渐变中间的灰度区域则表示按透明度显示这一层。
所以,渐变毛玻璃并不是让一个 blur() 自身渐变,而是:
不同 blur 半径的多层结果
+
每层不同范围的 mask
=
空间上逐渐变化的模糊效果2
3
4
5
从 FrostedSurface 看多层接力
当前组件的实现正是这个思路。它准备了四组配置:
const FROSTED_LAYER_CONFIG_LIST = [
{ blur: '1px', solidEnd: '85%', transparentEnd: '100%' },
{ blur: '3px', solidEnd: '61%', transparentEnd: '81%' },
{ blur: '5px', solidEnd: '36%', transparentEnd: '56%' },
{ blur: '7px', solidEnd: '12%', transparentEnd: '32%' },
];2
3
4
5
6
每组配置包含两个信息:
blur:当前层的模糊半径;solidEnd和transparentEnd:当前层从完全显示过渡到完全透明的位置。
组件再把配置转换为类似下面的 CSS 变量:
.frosted-layer {
--blur: 5px;
--mask: linear-gradient(
to bottom,
#000 0%,
#000 36%,
transparent 56%
);
backdrop-filter: blur(var(--blur));
-webkit-backdrop-filter: blur(var(--blur));
-webkit-mask: var(--mask);
mask: var(--mask);
}2
3
4
5
6
7
8
9
10
11
12
13
14
这种设计有一个重要特点:相邻层的蒙版过渡区是重叠的。
例如,第二层在 61% 到 81% 之间淡出,第三层在 36% 到 56% 之间淡出。各层不是在同一条位置突然切换,而是通过重叠的透明度变化逐步接力。
如果把每层的蒙版写成完全不重叠的区间,就可能出现明显的横向分界线:某一层刚消失,下一层还没有完全出现,画面会出现断层。
模糊方向
渐变毛玻璃可以向上渐变,也可以向下渐变。改变方向时,通常只需要改变蒙版的方向:
/* 从上到下逐渐淡出这一层 */
mask: linear-gradient(to bottom, #000 0 60%, transparent 100%);
/* 从下到上逐渐淡出这一层 */
mask: linear-gradient(to top, #000 0 60%, transparent 100%);2
3
4
5
不要为每个方向复制一套层配置。更好的做法是让方向成为一个独立参数,所有模糊层复用同一方向,只保留各层不同的模糊半径和停止位置。
一个可以直接运行的 HTML 示例
下面的示例包含完整的 HTML 和内嵌 CSS。把它复制到任意 HTML 文件中即可看到效果。页面背景使用了几个彩色圆形,方便观察玻璃后方内容是如何逐渐变模糊的。
完整 HTML
BACKDROP FILTER / MASK
清晰的背景,逐渐变模糊。
向下滚动页面,观察底部固定区域:越靠近顶部,背后的网格、文字和卡片越清晰; 越靠近底部,模糊层越多,背景细节越柔和。
如果想让下方更模糊、上方更清晰,可以把层配置整体反过来,或者把 to bottom 改成 to top。关键是让更大的模糊半径只出现在需要增强模糊的区域。
为什么推荐多层,而不是一层渐变背景
有些实现会写成这样:
.glass {
background: linear-gradient(
to bottom,
rgb(255 255 255 / 4%),
rgb(255 255 255 / 70%)
);
backdrop-filter: blur(12px);
}2
3
4
5
6
7
8
这段代码可以产生“颜色渐变”,但不能产生“模糊强度渐变”。整个元素仍然使用同一个 blur(12px),变化的只是白色覆盖层的透明度。
颜色渐变有时已经够用,但它和真正的渐变毛玻璃有明显区别:
| 实现方式 | 变化的是什么 | 背景细节的模糊程度 |
|---|---|---|
| 渐变背景 + 单层 blur | 覆盖色的透明度 | 始终相同 |
| 多层 blur + mask | 每层模糊结果的可见范围 | 沿方向变化 |
如果需求只是让区域下方颜色更白,第一种写法更简单;如果需要让背景细节也从清晰逐渐变模糊,就需要多层 backdrop-filter。
mask-image 和 backdrop-filter 容易踩的坑
mask 不是背景色
.layer {
mask-image: linear-gradient(to bottom, #000, transparent);
}2
3
这里的黑色不会显示在页面上。mask 做的是透明度裁剪:黑色区域显示元素,透明区域隐藏元素,灰色区域半透明显示元素。
因此,单独给一个空元素加 mask-image,通常看不到任何东西。需要先确认这个元素本身确实有可见内容,例如 backdrop-filter 或半透明背景。
mask-image 和 mask 的关系
mask 是一个 shorthand,除了图像外,还可以统一描述遮罩的位置、大小、重复方式等属性。简单场景可以写 mask-image,但在需要兼容 Safari 或维护多个相关属性时,推荐成对写:
.layer {
-webkit-mask: var(--mask);
mask: var(--mask);
}2
3
4
这里的 -webkit-mask 不是为了替代标准属性,而是为了覆盖部分 WebKit 内核环境。两者最好使用同一份变量,避免前缀版本和标准版本的渐变参数不一致。
不要把 mask-composite 当成渐变必需品
mask-composite 用来规定多个蒙版之间如何合成,例如相加、相减或相交。
本文的渐变毛玻璃只需要每个模糊层使用一个线性渐变蒙版,因此不需要 mask-composite。只有在做镂空、边框蒙版或多个遮罩相减时,才需要引入它。
不必要地加入 mask-composite 会增加浏览器差异,尤其是不同内核对组合关键字和前缀的支持并不完全一致。
backdrop-filter 处理的是背后的内容
backdrop-filter 处理的是元素背后的区域,不是当前元素中的文字和子元素。
因此,下面的元素需要是半透明的:
.glass {
background: rgb(255 255 255 / 12%);
backdrop-filter: blur(12px);
}2
3
4
如果写成 background: #fff,背景内容会被不透明白色盖住,模糊效果就很难观察到。调试时可以先使用非常低的透明度,例如 rgb(255 255 255 / 8%)。
圆角和溢出裁剪要放在外层
渐变模糊层一般会使用绝对定位铺满容器:
.frosted-panel {
position: relative;
overflow: hidden;
border-radius: 28px;
}
.frosted-layer {
position: absolute;
inset: 0;
}2
3
4
5
6
7
8
9
10
外层的 overflow: hidden 负责裁剪模糊层,border-radius 负责让内部效果跟随容器圆角。否则模糊可能超出卡片边界,四角出现不规则的矩形区域。
如果页面中还有其他定位层,建议让玻璃容器建立独立层叠上下文:
.frosted-panel {
isolation: isolate;
}2
3
isolation 可以减少玻璃装饰层和外部元素发生意外混合的问题,但它不能替代对 z-index 和 DOM 层级的检查。
如何调节渐变效果
渐变毛玻璃主要调四类参数。
模糊半径
--blur: 1px;
--blur: 3px;
--blur: 7px;2
3
模糊半径差距太小,视觉上不容易看出渐变;差距太大,又容易出现层次断裂。通常可以从 1px / 3px / 5px / 7px 这类小步进开始,再根据背景对比度调整。
实色区位置
linear-gradient(to bottom, #000 0 60%, transparent 100%);60% 之前是完全显示区域。这个值越大,该层影响的范围越大。
透明过渡区
linear-gradient(to bottom, #000 0 60%, transparent 85%);从 60% 到 85% 是过渡区。过渡区太短会显得生硬,太长则会让多层效果变得过于平均。相邻层的过渡区应该有一定重叠。
渐变方向
to bottom
to top2
底部固定栏通常会让模糊从内容区域向底部增强;顶部导航则可能需要反方向。不要直接照搬停止点,要根据组件所在的位置决定方向。
性能和兼容性
模糊层越多,成本越高
多层方案的代价是需要多次处理背景。四层可以提供比较平滑的过渡,但并不意味着所有场景都应该固定使用四层。
如果容器面积大、页面滚动频繁,或者运行在性能较弱的移动 WebView 中,可以降为一层:
.frosted-panel--simple {
background: rgb(255 255 255 / 22%);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.frosted-panel--simple .frosted-layer {
display: none;
}2
3
4
5
6
7
8
9
提供不支持时的背景
不支持 backdrop-filter 的浏览器至少应该有一个可读的背景:
.frosted-panel {
background: rgb(238 244 255 / 92%);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.frosted-panel {
background: rgb(255 255 255 / 8%);
}
}2
3
4
5
6
7
8
9
这样即使模糊没有生效,文字和按钮也不会直接落在复杂背景上。
不要只用桌面浏览器截图判断性能
静态截图只能说明效果是否大致正确,不能说明滚动时是否流畅。实际验证时至少要关注:
固定玻璃层是否覆盖了过大的区域;滚动时是否出现明显掉帧;Safari、移动浏览器和目标 WebView 是否都能看到模糊;不支持 backdrop-filter 时文字是否仍然清晰。
排查顺序
如果渐变毛玻璃没有效果,可以按这个顺序检查:
- 玻璃层后面是否有明显的彩色或高对比度内容;
- 玻璃层是否使用了半透明背景,而不是完全不透明的背景;
- 是否同时写了
backdrop-filter和-webkit-backdrop-filter; - 是否同时写了
mask和-webkit-mask; - 临时删除蒙版后,单层模糊是否可以正常显示;
- 外层是否设置了正确的尺寸、圆角和
overflow: hidden; - 各层的蒙版过渡区是否有重叠;
- 最后再检查
z-index、层叠上下文和浏览器支持情况。
调试时不要一开始同时修改模糊半径、透明度、蒙版方向和层级。先验证单层 backdrop-filter,再加 mask,最后增加其他模糊层,问题会更容易定位。
总结
渐变毛玻璃的重点,不是找到一个“神奇的 CSS 属性”,而是把一个模糊效果拆成多个强度不同的层:
backdrop-filter负责把背景内容模糊;- mask 负责控制每一层在哪些区域可见;
- 多层不同的 blur 半径负责形成强度差异;
- 重叠的渐变蒙版负责让层与层之间平滑接力;
- 半透明背景、圆角和降级背景负责保证最终可用性。
- 如果只是需要一个普通的半透明卡片,一层 backdrop-filter 就足够了;如果需要让固定区域和页面内容自然过渡,再考虑多层模糊与蒙版。
最实用的经验是:先用两层或三层做出方向正确的效果,再根据背景复杂度调整模糊半径和蒙版停止点。层数越多不一定越好,真正影响观感的是层之间是否连续,以及页面在真实设备上是否仍然流畅。
