CSS mask 完全指南:从渐变裁切到弹幕遮挡

0 阅读13分钟

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 换成 redblue 或任何颜色,效果完全一样。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-sizebackground-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 渐变本身不支持 transitionanimation。也就是说你写 transition: mask 0.3s 是没用的,linear-gradient 内部的参数变化不会有平滑过渡。

两种绕过方案:

  1. 逐帧动画:用 SASS 循环生成 0% 到 100% 共 101 帧的 @keyframes,每一帧写死 mask 的值
  2. 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。

原理

  1. 视频画面和弹幕容器是两层叠加结构,弹幕在上层
  2. 后端通过图像识别算法,实时计算出人物的轮廓区域
  3. 生成一张 SVG/PNG 图片:人物轮廓区域是透明的,其他区域是白色/实色的
  4. 把这张图片设为弹幕容器的 mask-image
  5. 根据 mask 的工作原理——透明区域对应的弹幕内容不可见——弹幕就"消失"在人物背后了
  6. 随着视频播放,后端不断更新 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 没有的能力。


延伸阅读