SVG 的 clipPath 裁剪和 mask 蒙版有什么区别?怎么用?
SVG 里有两套视觉裁切机制:clipPath 和 mask。前者做硬边缘裁剪,后者做透明度遮罩。前端面试经常考两者的区别,实际开发中圆形头像、文字镂空、渐变淡出也都靠它们实现。这篇文章从语法、属性、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:透明度遮罩
mask 和 clipPath 最大的区别在于: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 的核心区别
| 对比项 | clipPath | mask |
|---|---|---|
| 裁切方式 | 硬边缘,非显即隐 | 支持透明度渐变 |
| 透明度控制 | 无 | 白=显示,黑=隐藏,灰=半透明 |
| 事件响应 | 裁剪区域外不响应事件 | 遮罩透明区域仍可响应事件 |
| 性能 | 更好,计算简单 | 较重,需要逐像素计算 |
| 典型场景 | 形状裁剪、头像、镂空文字 | 渐变淡出、聚光灯、阴影遮罩 |
面试时记住一句话: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 节点。
浏览器兼容性
clipPath 和 mask 在现代浏览器中支持良好,但有几个坑要注意:
- Firefox 对
clipPath内使用<use>引用外部形状有历史 bug - Safari 对 CSS
mask需要加-webkit-前缀 clipPathUnits="objectBoundingBox"在老版本 WebKit 中可能有精度问题- CSS
clip-path作用于 HTML 元素时,Firefox 早期版本需要额外的 SVG 引用处理
生产环境中建议在关键路径上做特性检测或提供降级方案,Safari 的 -webkit- 前缀别忘了加。
实际业务中怎么选
选型其实不复杂,记住两条原则:
- 只要不需要透明度过渡,就用
clipPath。性能好,语义清晰,浏览器计算快。 - 需要渐变、半透明、软边缘时,才上
mask。代价是渲染开销更大。
几个典型业务场景的选型参考:
- 用户头像裁成圆形 →
clipPath,硬边缘就够了 - 文字 banner 镂空渐变 →
clipPath做文字裁剪 + 渐变填充 - 卡片底部淡出效果 →
mask,需要从实到虚的渐变 - 鼠标跟随聚光灯 →
mask+ 径向渐变 - 斜切/波浪形分割线 →
clipPath或 CSSclip-path: polygon(),不需要半透明
如果项目已经大量使用 CSS clip-path 和 mask-image,SVG 定义可以作为复杂路径的补充,两者并不冲突。