5月27日 15:35

SVG 的 clipPath 裁剪和 mask 蒙版有什么区别?怎么用?

SVG 里有两套视觉裁切机制:clipPathmask。前者做硬边缘裁剪,后者做透明度遮罩。前端面试经常考两者的区别,实际开发中圆形头像、文字镂空、渐变淡出也都靠它们实现。这篇文章从语法、属性、CSS 联动到实战案例,逐一拆解。

clipPath:硬边缘裁剪

clipPath 的逻辑很简单:定义一个封闭区域,区域内的内容保留,区域外的内容直接消失,不存在半透明过渡。

基本语法

svg
<svg width="200" height="200"> <defs> <clipPath id="circleClip"> <circle cx="100" cy="100" r="80" /> </clipPath> </defs> <rect width="200" height="200" fill="#4A90D9" clip-path="url(#circleClip)" /> </svg>

clipPath 内部放什么形状,就按什么形状裁。矩形、圆形、多边形、文字都可以。

clipPathUnits 属性

这个属性决定了裁剪路径的坐标系,默认值是 userSpaceOnUse

  • userSpaceOnUse:裁剪路径使用元素所在的用户坐标系。clipPath 内的坐标值是绝对坐标,跟被裁剪元素的位置无关。
  • objectBoundingBox:裁剪路径使用被裁剪元素的包围盒作为参考系,坐标值范围 0~1,表示相对比例。
svg
<defs> <!-- objectBoundingBox 模式:0.5 表示元素宽度/高度的 50% --> <clipPath id="halfClip" clipPathUnits="objectBoundingBox"> <rect x="0" y="0" width="0.5" height="1" /> </clipPath> </defs> <rect x="20" y="20" width="160" height="160" fill="#E74C3C" clip-path="url(#halfClip)" />

objectBoundingBox 适合做"裁掉左半边/下半边"这类比例裁切,不用算具体像素。

clip-rule 属性

clip-rule 控制路径内部的填充判定规则,和 SVG 的 fill-rule 一致:

  • nonzero(默认):非零环绕规则,适合普通形状
  • evenodd:奇偶规则,适合有镂空的复合形状
svg
<defs> <clipPath id="ringClip"> <!-- evenodd 让环形区域被裁剪保留,内部圆被镂空 --> <path clip-rule="evenodd" d="M100,20 A80,80 0 1,1 99.9,20 Z M100,50 A50,50 0 1,0 99.9,50 Z" /> </clipPath> </defs> <rect width="200" height="200" fill="#9B59B6" clip-path="url(#ringClip)" />

用文字做裁剪路径

把文字作为 clipPath 的形状,可以让背景图片或渐变只在文字轮廓内显示,这种效果在 banner 设计中很常见。

svg
<svg width="400" height="120"> <defs> <linearGradient id="textGrad" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#FF6B6B" /> <stop offset="100%" stop-color="#4ECDC4" /> </linearGradient> <clipPath id="textClip"> <text x="200" y="85" font-size="72" font-weight="bold" text-anchor="middle">SVG</text> </clipPath> </defs> <rect width="400" height="120" fill="url(#textGrad)" clip-path="url(#textClip)" /> </svg>

裁剪图片实现圆形头像

svg
<svg width="160" height="160"> <defs> <clipPath id="avatarClip"> <circle cx="80" cy="80" r="76" /> </clipPath> </defs> <image href="/avatar.jpg" x="0" y="0" width="160" height="160" clip-path="url(#avatarClip)" /> </svg>

mask:透明度遮罩

maskclipPath 最大的区别在于:mask 不是非黑即白的,它用灰度值控制透明度。白色区域完全显示,黑色区域完全隐藏,中间灰度值对应半透明。

基本语法

svg
<svg width="200" height="200"> <defs> <mask id="holeMask"> <rect width="200" height="200" fill="white" /> <circle cx="100" cy="100" r="50" fill="black" /> </mask> </defs> <rect width="200" height="200" fill="#4A90D9" mask="url(#holeMask)" /> </svg>

白色背景 + 黑色圆 = 蓝色矩形中间被挖了一个圆洞。

mask-type 属性:luminance 还是 alpha

mask-type 决定了 mask 的计算方式:

  • luminance(默认):根据颜色的亮度值计算透明度。白色=不透明,黑色=透明,灰色=半透明
  • alpha:直接使用颜色的 alpha 通道,忽略颜色本身
svg
<defs> <!-- alpha 模式:只看 alpha 通道,颜色不重要 --> <mask id="alphaMask" mask-type="alpha"> <rect width="200" height="200" fill="rgba(255,0,0,1)" /> <circle cx="100" cy="100" r="60" fill="rgba(0,0,0,0)" /> </mask> </defs>

CSS 中对应的是 mask-mode 属性(match-source | luminance | alpha)。实际开发中 alpha 模式更直觉——直接设置 rgba 的透明度就好,不用去算灰度。

maskUnits 和 maskContentUnits

这是两个容易混淆的属性:

  • maskUnits(默认 objectBoundingBox):控制 mask 元素自身定位框的坐标系。决定 mask 的 x、y、width、height 参照什么
  • maskContentUnits(默认 userSpaceOnUse):控制 mask 内部子元素的坐标系。决定你画的那些 rect、circle 的坐标参照什么
svg
<defs> <!-- mask 自身定位按用户坐标,内容也按用户坐标 --> <mask id="m1" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse"> <rect x="0" y="0" width="200" height="200" fill="white" /> </mask> </defs>

大多数场景下用默认值就够了,只有在需要 mask 跟随元素尺寸自动缩放时才需要调整。

渐变蒙版实现淡出效果

这是 mask 最典型的应用场景——让元素从一侧渐变消失,clipPath 做不到这种软边缘。

svg
<svg width="300" height="100"> <defs> <linearGradient id="fadeGrad" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="white" /> <stop offset="100%" stop-color="black" /> </linearGradient> <mask id="fadeMask"> <rect width="300" height="100" fill="url(#fadeGrad)" /> </mask> </defs> <rect width="300" height="100" fill="#2ECC71" mask="url(#fadeMask)" /> </svg>

用径向渐变做聚光灯效果

svg
<svg width="300" height="200"> <defs> <radialGradient id="spotGrad" cx="50%" cy="50%" r="50%"> <stop offset="0%" stop-color="white" /> <stop offset="100%" stop-color="black" /> </radialGradient> <mask id="spotMask"> <rect width="300" height="200" fill="url(#spotGrad)" /> </mask> </defs> <image href="/scene.jpg" width="300" height="200" mask="url(#spotMask)" /> </svg>

clipPath 和 mask 的核心区别

对比项clipPathmask
裁切方式硬边缘,非显即隐支持透明度渐变
透明度控制白=显示,黑=隐藏,灰=半透明
事件响应裁剪区域外不响应事件遮罩透明区域仍可响应事件
性能更好,计算简单较重,需要逐像素计算
典型场景形状裁剪、头像、镂空文字渐变淡出、聚光灯、阴影遮罩

面试时记住一句话:clipPath 是剪刀,mask 是滤镜。剪刀只有剪和不剪,滤镜可以调透明度。

clipPath 能否嵌套?mask 能否叠加?

  • clipPath 嵌套:一个元素只能有一个 clip-path,但 clipPath 内部可以放多个形状,取并集作为裁剪区域。如果需要交集裁剪,可以把裁剪结果包一层 group 再裁剪。
  • mask 叠加:一个元素只能有一个 mask,多个 mask 需要手动合并到同一个 <mask> 元素内,通过叠加绘制实现组合效果。

CSS clip-path 与 SVG clipPath 的联动

CSS 的 clip-path 属性可以直接引用 SVG 中定义的 clipPath,这让 SVG 裁剪能作用于普通 HTML 元素。

html
<!-- 定义 SVG 裁剪路径 --> <svg width="0" height="0"> <defs> <clipPath id="starClip"> <polygon points="50,5 20,95 95,35 5,35 80,95" /> </clipPath> </defs> </svg> <!-- 在 HTML 元素上引用 --> <div style="width:200px;height:200px;background:#E74C3C;clip-path:url(#starClip)"></div>

CSS 也支持直接使用基本形状函数,不需要定义 SVG:

css
.avatar { clip-path: circle(50%); } .banner { clip-path: polygon(0 0, 100% 0, 100% 80%, 0 100%); }

但自定义复杂路径仍然需要 SVG clipPath。两者配合使用是最灵活的方案。

CSS mask 属性同样可以引用 SVG mask,还可以用图片做遮罩:

css
.card { mask-image: linear-gradient(to bottom, white, transparent); -webkit-mask-image: linear-gradient(to bottom, white, transparent); }

组合使用:圆形裁剪 + 渐变淡出

svg
<svg width="200" height="200"> <defs> <clipPath id="circleClip"> <circle cx="100" cy="100" r="80" /> </clipPath> <linearGradient id="maskGrad" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="white" /> <stop offset="100%" stop-color="black" /> </linearGradient> <mask id="fadeMask"> <rect width="200" height="200" fill="url(#maskGrad)" /> </mask> </defs> <rect width="200" height="200" fill="#4A90D9" clip-path="url(#circleClip)" mask="url(#fadeMask)" /> </svg>

先用 clipPath 把矩形裁成圆形,再用 mask 让圆形从左到右渐变消失。

动态裁剪与动画

通过 JavaScript 修改 clipPath 内元素的属性,可以实现动画效果:

svg
<svg width="200" height="200"> <defs> <clipPath id="dynamicClip"> <circle id="clipCircle" cx="100" cy="100" r="50" /> </clipPath> </defs> <rect width="200" height="200" fill="#4A90D9" clip-path="url(#dynamicClip)" /> </svg> <script> const circle = document.getElementById('clipCircle'); let r = 50, growing = true; function animate() { r += growing ? 0.5 : -0.5; if (r >= 80) growing = false; if (r <= 30) growing = true; circle.setAttribute('r', r); requestAnimationFrame(animate); } animate(); </script>

如果只需要 CSS 动画,也可以用 CSS clip-path 配合 @keyframes

css
.box { clip-path: circle(30%); animation: breathe 3s ease-in-out infinite alternate; } @keyframes breathe { to { clip-path: circle(80%); } }

CSS 动画方案更轻量,适合简单的缩放裁切动画。需要路径变形等复杂动画时,才需要回到 JavaScript 操作 SVG 节点。

浏览器兼容性

clipPathmask 在现代浏览器中支持良好,但有几个坑要注意:

  • Firefox 对 clipPath 内使用 <use> 引用外部形状有历史 bug
  • Safari 对 CSS mask 需要加 -webkit- 前缀
  • clipPathUnits="objectBoundingBox" 在老版本 WebKit 中可能有精度问题
  • CSS clip-path 作用于 HTML 元素时,Firefox 早期版本需要额外的 SVG 引用处理

生产环境中建议在关键路径上做特性检测或提供降级方案,Safari 的 -webkit- 前缀别忘了加。

实际业务中怎么选

选型其实不复杂,记住两条原则:

  1. 只要不需要透明度过渡,就用 clipPath。性能好,语义清晰,浏览器计算快。
  2. 需要渐变、半透明、软边缘时,才上 mask。代价是渲染开销更大。

几个典型业务场景的选型参考:

  • 用户头像裁成圆形 → clipPath,硬边缘就够了
  • 文字 banner 镂空渐变 → clipPath 做文字裁剪 + 渐变填充
  • 卡片底部淡出效果 → mask,需要从实到虚的渐变
  • 鼠标跟随聚光灯 → mask + 径向渐变
  • 斜切/波浪形分割线 → clipPath 或 CSS clip-path: polygon(),不需要半透明

如果项目已经大量使用 CSS clip-pathmask-image,SVG 定义可以作为复杂路径的补充,两者并不冲突。

标签:SVG