CSS mask 完全指南:从渐变裁切到弹幕遮挡
CSS 属性里,mask 大概是被低估最严重的那一个。很多人知道它能"遮住一些东西",但真正上手时又觉得无从下手。其实 mask 的语法和 background 几乎一模一样——如果你已经玩转了渐变背景,那 mask 对你来说就是换个属性名的事。
本文会从语法开始,一路讲到弹幕遮挡、转场动画这些实战场景。每个案例都附带可运行的代码。
1. mask 到底是什么?
一句话:mask 决定元素的哪些部分可见、哪些部分透明。
它接受的值和 background 一样——渐变、图片、SVG 都行。工作原理也简单:
- mask 中有颜色的区域(不管什么颜色),对应元素内容可见
- mask 中透明的区域,对应元素内容不可见
来看最基础的例子:
.demo {
background: url(photo.jpg);
-webkit-mask: linear-gradient(90deg, transparent, #000);
mask: linear-gradient(90deg, transparent, #000);
}
效果是图片从左侧完全透明,到右侧完全可见——一个从无到有的渐隐效果。
这里 #000 换成 red、blue 或任何颜色,效果完全一样。mask 只关心透明度,不关心色相。
2. mask 语法详解
根据 MDN CSS mask 文档:
The mask shorthand CSS property hides an element (partially or fully) by masking or clipping the image at specific points. It is a shorthand for mask-image, mask-mode, mask-repeat, mask-position, mask-clip, mask-origin, mask-size, and mask-composite.
mask 是一个简写属性,包含以下子属性:
| 子属性 | 作用 | 对应的 background 属性 |
|---|---|---|
mask-image |
遮罩图像(渐变/图片/SVG) | background-image |
mask-size |
遮罩尺寸 | background-size |
mask-repeat |
是否平铺 | background-repeat |
mask-position |
遮罩定位 | background-position |
mask-origin |
定位参考框 | background-origin |
mask-clip |
裁切参考框 | background-clip |
mask-composite |
多个遮罩的合成方式 | 无对应属性 |
看到没有?除了 mask-composite,其他属性和 background 完全对应。如果你已经熟悉了 background-size、background-position 这些属性,mask 的学习成本几乎为零。
兼容性前缀
目前(2026 年)在 Chrome、Edge 等 Blink 内核浏览器中,mask 仍需 -webkit- 前缀。实际写代码时建议这样写:
.el {
-webkit-mask: linear-gradient(#000, transparent);
mask: linear-gradient(#000, transparent);
}
或者直接在构建工具中配置 autoprefixer,让它帮你加前缀。
3. 基础用法:渐变遮罩裁切
3.1 案例:图片切角效果
多层线性渐变可以拼出切角图形,这个技巧在 background 上就能用。把同样的渐变写到 mask 里,就能把任意元素裁成切角造型——不管元素里面是图片、文字还是渐变背景。
.notch-image {
width: 300px;
height: 200px;
background: url(https://picsum.photos/300/200) no-repeat center/cover;
-webkit-mask:
linear-gradient(135deg, transparent 15px, #fff 0) top left,
linear-gradient(-135deg, transparent 15px, #fff 0) top right,
linear-gradient(-45deg, transparent 15px, #fff 0) bottom right,
linear-gradient(45deg, transparent 15px, #fff 0) bottom left;
-webkit-mask-size: 50% 50%;
-webkit-mask-repeat: no-repeat;
mask:
linear-gradient(135deg, transparent 15px, #fff 0) top left,
linear-gradient(-135deg, transparent 15px, #fff 0) top right,
linear-gradient(-45deg, transparent 15px, #fff 0) bottom right,
linear-gradient(45deg, transparent 15px, #fff 0) bottom left;
mask-size: 50% 50%;
mask-repeat: no-repeat;
}
四个方向的渐变各占 50% 50%,拼在一起刚好覆盖整个元素。每个渐变在角落处用 transparent 挖掉一个三角形,组合起来就是四角切角。
这里的 #fff 0 用了渐变简写技巧:0 会被浏览器修正为前一个色标的位置 15px,形成硬边界。
![]()
3.2 案例:内切圆角按钮
普通的内切圆角用 radial-gradient 就能画出来。但问题在于:如果按钮背景是渐变色而不是纯色,直接用 background 画内切圆角基本无解——你没法让两层渐变"叠加"出一个圆角效果。
mask 能解决这个问题:把内切圆角的形状写成 mask,background 想用什么渐变都行。
.inset-btn {
padding: 16px 48px;
font-size: 16px;
color: #fff;
border: none;
background: linear-gradient(45deg, #2179f5, #e91e63);
-webkit-mask:
radial-gradient(
circle at 100% 100%,
transparent 0,
transparent 12px,
#fff 13px
)
right bottom,
radial-gradient(circle at 0 0, transparent 0, transparent 12px, #fff 13px)
left top,
radial-gradient(
circle at 100% 0,
transparent 0,
transparent 12px,
#fff 13px
)
right top,
radial-gradient(
circle at 0 100%,
transparent 0,
transparent 12px,
#fff 13px
)
left bottom;
-webkit-mask-size: 50% 50%;
-webkit-mask-repeat: no-repeat;
mask:
radial-gradient(
circle at 100% 100%,
transparent 0,
transparent 12px,
#fff 13px
)
right bottom,
radial-gradient(circle at 0 0, transparent 0, transparent 12px, #fff 13px)
left top,
radial-gradient(
circle at 100% 0,
transparent 0,
transparent 12px,
#fff 13px
)
right top,
radial-gradient(
circle at 0 100%,
transparent 0,
transparent 12px,
#fff 13px
)
left bottom;
mask-size: 50% 50%;
mask-repeat: no-repeat;
}
原理:四个 radial-gradient 分别处理四个角,每个径向渐变的圆心在对应角落,0~12px 的范围是透明的(挖出圆弧),13px 往外是白色(保留内容)。
改变 12px 的值可以调整圆弧大小。这种方案的好处是 background 完全自由——纯色、渐变、图片都没问题。
![]()
4. 进阶用法:渐变消失与融合
4.1 案例:横向滚动列表的渐变消失
在很多产品里都能看到这种效果:一个横向可滚动的列表,右侧内容渐渐消失,暗示用户"还有更多内容"。
不用 mask 的话你可能会想到覆盖一个半透明遮罩层。但这有个麻烦:遮罩层会挡住点击事件,还需要设置 pointer-events: none。
用 mask 就一行代码:
.scroll-list {
display: flex;
overflow-x: auto;
gap: 12px;
-webkit-mask: linear-gradient(90deg, #000 70%, transparent);
mask: linear-gradient(90deg, #000 70%, transparent);
}
linear-gradient(90deg, #000 70%, transparent) 的意思是:从左到右,前 70% 完全可见,剩下 30% 逐渐透明。就这么简单。
要注意一点:mask 作用于整个元素及其内容,包括文字、子元素、甚至滚动条。这正是 mask 和 "覆盖一层遮罩" 的本质区别——mask 是从元素自身出发做裁切,而不是在上面盖东西。
![]()
4.2 案例:两张图片融合
mask 做图片融合非常直观:两张图片叠在一起,上层图片加一个 mask,mask 的透明区域会露出下层图片。
.blend {
position: relative;
width: 400px;
height: 300px;
background: url(https://picsum.photos/400/300?random=1) no-repeat center/cover;
}
.blend::before {
content: '';
position: absolute;
inset: 0;
background: url(https://picsum.photos/400/300?random=2) no-repeat center/cover;
-webkit-mask: linear-gradient(45deg, #000 40%, transparent 60%);
mask: linear-gradient(45deg, #000 40%, transparent 60%);
}
linear-gradient(45deg, #000 40%, transparent 60%) 中,40% 到 60% 这段是过渡区——两张图片在这里平滑融合。如果你把它改成 #000 50%, transparent 50%,那就是硬切割,没有过渡。
除了 linear-gradient 做线性方向的融合,radial-gradient 可以做径向区域的融合——在画面中某个位置开一个"窗口",露出下层的内容:
.radial-blend {
position: relative;
width: 520px;
height: 320px;
overflow: hidden;
}
.radial-blend .layer-cold {
position: absolute;
inset: 0;
background: url(scene-cold.jpg) center / cover no-repeat;
}
.radial-blend .layer-warm {
position: absolute;
inset: 0;
background: url(scene-warm.jpg) center / cover no-repeat;
-webkit-mask: radial-gradient(circle at 35% 45%, #000 20%, transparent 60%);
mask: radial-gradient(circle at 35% 45%, #000 20%, transparent 60%);
}
上层暖色调图片通过 radial-gradient 只在左侧偏上的位置可见,向外逐渐透明,露出底层冷色调图片。两张风格不同的照片在圆形过渡区自然融合。
![]()
5. mask-composite:组合遮罩
当一个元素有多个 mask 时,mask-composite 决定它们之间怎么合成。
根据 MDN mask-composite 文档:
The mask-composite CSS property represents a compositing operation used on the current mask layer with the mask layers below it.
标准语法支持四个关键字:
mask-composite: add; /* 叠加(默认)*/
mask-composite: subtract; /* 减去 */
mask-composite: intersect; /* 取交集 */
mask-composite: exclude; /* 排除重叠 */
但 WebKit 浏览器用的是另一套语法(-webkit-mask-composite),常用的值有:
-webkit-mask-composite: source-over; /* 对应 add */
-webkit-mask-composite: source-in; /* 对应 intersect */
-webkit-mask-composite: source-out; /* 只显示上层独有部分 */
-webkit-mask-composite: destination-out; /* 只显示下层独有部分 */
-webkit-mask-composite: xor; /* 对应 exclude */
案例:两个圆弧取交集
假设你想裁出一个"两个圆弧重叠"的形状:
.composite-demo {
width: 300px;
height: 200px;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-mask:
radial-gradient(circle at 100% 0, #000, #000 200px, transparent 200px),
radial-gradient(circle at 0 0, #000, #000 200px, transparent 200px);
-webkit-mask-composite: source-in;
mask:
radial-gradient(circle at 100% 0, #000, #000 200px, transparent 200px),
radial-gradient(circle at 0 0, #000, #000 200px, transparent 200px);
mask-composite: intersect;
}
如果不加 mask-composite,两个 mask 默认是 add(叠加),你看到的是两个圆弧的并集。加上 intersect(或 -webkit-mask-composite: source-in),就只保留两个圆弧重叠的部分。
这个能力在做异形裁切时很有用:单个渐变很难画出的形状,可以通过多个简单渐变组合得到。
![]()
6. 高阶动画:mask 驱动的转场
mask 不只是静态裁切。通过动态改变 mask 的值,可以实现各种转场和切换效果。
6.1 渐变不能直接做动画——怎么办?
CSS 渐变本身不支持 transition 和 animation。也就是说你写 transition: mask 0.3s 是没用的,linear-gradient 内部的参数变化不会有平滑过渡。
两种绕过方案:
-
逐帧动画:用 SASS 循环生成 0% 到 100% 共 101 帧的
@keyframes,每一帧写死 mask 的值 -
CSS @property:注册一个自定义属性,让浏览器知道这个变量是
<percentage>类型,这样它就能被动画插值
第一种方案的代码经过 SASS 编译后非常臃肿(101 帧)。推荐用第二种。
6.2 案例:conic-gradient 扇形转场(CSS @property 方案)
这是一个经典的转场效果:上层图片像扇形展开一样逐渐覆盖下层图片。hover 时触发动画。
@property --conic-p {
syntax: '<percentage>';
inherits: false;
initial-value: -10%;
}
.transition-box {
position: relative;
width: 400px;
height: 400px;
background: url(https://picsum.photos/400/400?random=5) no-repeat center/cover;
cursor: pointer;
}
.transition-box::before {
content: '';
position: absolute;
inset: 0;
background: url(https://picsum.photos/400/400?random=100) no-repeat
center/cover;
pointer-events: none;
-webkit-mask: conic-gradient(
#000 0,
#000 var(--conic-p),
transparent calc(var(--conic-p) + 10%),
transparent
);
mask: conic-gradient(
#000 0,
#000 var(--conic-p),
transparent calc(var(--conic-p) + 10%),
transparent
);
}
.transition-box:hover::before {
animation: conicSweep 1.5s ease-in-out forwards;
}
@keyframes conicSweep {
from {
--conic-p: -10%;
}
to {
--conic-p: 100%;
}
}
这里有几个关键点:
-
@property --conic-p:注册之后,浏览器知道--conic-p是百分比类型,可以在动画中平滑插值。mask 里的conic-gradient会随着--conic-p从 -10% 变化到 100%,像时钟指针一样扫过整个圆。 -
pointer-events: none:伪元素覆盖在容器上层,如果不加这个属性,鼠标事件会被伪元素拦截,导致容器的:hover状态无法触发。 -
calc(var(--conic-p) + 10%)多出的 10% 是过渡区,让边缘不那么生硬。如果你想要硬边界,把+10%去掉就行。
同样的思路,换成 linear-gradient 就是一个从左到右的滑动转场:
@property --slide-p {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
.slide-box {
position: relative;
width: 400px;
height: 400px;
background: url(https://picsum.photos/400/400?random=10) no-repeat
center/cover;
cursor: pointer;
}
.slide-box::before {
content: '';
position: absolute;
inset: 0;
background: url(https://picsum.photos/400/400?random=200) no-repeat
center/cover;
pointer-events: none;
-webkit-mask: linear-gradient(
90deg,
#000 var(--slide-p),
transparent calc(var(--slide-p) + 5%)
);
mask: linear-gradient(
90deg,
#000 var(--slide-p),
transparent calc(var(--slide-p) + 5%)
);
}
.slide-box:hover::before {
animation: slideReveal 1.2s ease-in-out forwards;
}
@keyframes slideReveal {
from {
--slide-p: 0%;
}
to {
--slide-p: 100%;
}
}
和扇形转场的原理完全一样,只是把 conic-gradient 换成了 linear-gradient。--slide-p 从 0% 变化到 100%,实色区域从左往右推进,形成滑动揭示的效果。
如果你的目标浏览器不支持 @property(比如旧版 Firefox),也可以用 SASS 逐帧方案替代:
@keyframes maskSlide {
@for $i from 0 through 100 {
#{$i}% {
mask: linear-gradient(
90deg,
#000 #{$i + '%'},
transparent #{$i + 5 + '%'}
);
}
}
}
编译后会生成 101 帧的 @keyframes,每一帧写死 mask 的值,代码量大但兼容性最好。
![]()
7. 实战:弹幕人物遮挡效果
在 BiliBili 或虎牙直播中,弹幕经过人物区域时会自动"绕道"——弹幕看起来在人物的后面。这个效果的实现原理就是 mask。
原理
- 视频画面和弹幕容器是两层叠加结构,弹幕在上层
- 后端通过图像识别算法,实时计算出人物的轮廓区域
- 生成一张 SVG/PNG 图片:人物轮廓区域是透明的,其他区域是白色/实色的
- 把这张图片设为弹幕容器的
mask-image - 根据 mask 的工作原理——透明区域对应的弹幕内容不可见——弹幕就"消失"在人物背后了
- 随着视频播放,后端不断更新 mask 图片,实现实时遮挡
简化模拟
后端的实时图像识别我们没法在前端模拟,但原理可以用 radial-gradient 来演示:
.barrage-container {
position: absolute;
inset: 0;
-webkit-mask: radial-gradient(
circle at 100px 100px,
transparent 60px,
#fff 80px,
#fff 100%
);
mask: radial-gradient(
circle at 100px 100px,
transparent 60px,
#fff 80px,
#fff 100%
);
animation: maskFollow 6s infinite alternate linear;
}
@keyframes maskFollow {
to {
-webkit-mask-position: 80vw 0;
mask-position: 80vw 0;
}
}
radial-gradient 在 (100px, 100px) 位置挖了一个半径 60px 的圆形透明区域,60px 到 80px 是过渡,80px 以外完全可见。通过动画移动 mask-position,这个"挖洞"就会跟着移动。
真实场景中,这个 "挖洞" 的形状不是简单的圆形,而是从后端返回的人物轮廓 SVG。但 mask 的使用方式完全相同。
要搞清楚一点:mask 遮挡的是弹幕容器,不是人物。mask 的透明区域让弹幕不可见,从而"露出"弹幕下方的人物画面。
![]()
9. 兼容性
mask 属性的浏览器支持已经相当好了:
| 浏览器 | 支持情况 |
|---|---|
| Chrome / Edge | 支持(需 -webkit- 前缀) |
| Firefox | 完全支持(无需前缀) |
| Safari | 支持(需 -webkit- 前缀) |
| IE | 不支持 |
如果你不需要兼容 IE,mask 可以放心用。前缀问题交给 autoprefixer 处理:
// postcss.config.js
module.exports = {
plugins: [require('autoprefixer')],
};
mask-composite 的兼容性稍差一些,使用前建议在 Can I Use 上确认目标浏览器的支持情况。
10. 总结
核心原则
mask 中有颜色 → 内容可见,透明 → 内容不可见。记住这一条就够了。
技巧速查表
| 技巧 | 实现方式 | 典型场景 |
|---|---|---|
| 渐变遮罩 | mask: linear-gradient(...) |
内容淡出、列表渐隐 |
| 切角/异形裁切 | 多重 linear-gradient + mask-size: 50% 50%
|
图片切角、优惠券造型 |
| 内切圆角 | 多重 radial-gradient
|
不规则按钮、卡片 |
| 图片融合 | 伪元素叠加 + mask
|
两图过渡、径向区域融合 |
| 组合遮罩 | mask-composite: intersect |
多 mask 取交集/差集 |
| 渐变动画转场 |
@property + conic-gradient
|
扇形展开、滑动切换 |
| 图表重绘 |
@property + conic-gradient + :hover
|
数据可视化 hover 效果 |
| 弹幕遮挡 |
radial-gradient / 实时图片 |
视频直播弹幕 |
| 雪碧图转场 |
mask: url(sprite.png) + steps()
|
精致页面转场 |
和 background 的关系
mask 的语法和 background 几乎一一对应——多层叠加、repeat、position、size 这些在 background 上能做的事,mask 上全能做。多出来的 mask-composite 让多个 mask 之间的布尔运算成为可能,这是 background 没有的能力。
延伸阅读
- MDN CSS mask - mask 属性完整文档
- MDN mask-composite - 组合遮罩文档
- MDN CSS @property - CSS 自定义属性注册
- Can I Use: CSS Masks - mask 浏览器兼容性查询