跳到主要内容

渐变Pattern与滤镜

渐变和 pattern 的坐标在 gradientUnits / patternUnits 里,不在你想象的「贴在物体上」。滤镜会把矢量子树打成中间位图。三者都是生产里对齐失败和掉帧的高发区。

1. objectBoundingBox 与零尺寸

默认 gradientUnits="objectBoundingBox"x1 y1 x2 y2 是包围盒上的 0..1 比例,不是用户单位。

<linearGradient id="g" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#3b5bfd"/>
<stop offset="1" stop-color="#9b87f5"/>
</linearGradient>

包围盒宽或高为 0(水平 line、零高度 rect、退化 transform)时,按规范这个坐标系建不出来,渐变经常变成透明或纯黑,三引擎表现还不一致。

export function canUseObjectBoundingBox(el: SVGGraphicsElement): boolean {
const box = el.getBBox({ fill: true, stroke: false, markers: false, clipped: false });
return box.width > 0 && box.height > 0;
}

细线、坐标轴、1px 分隔:改 userSpaceOnUse,端点写用户单位;或给几何一个最小厚度再填。

userSpaceOnUse 的坐标活在 引用时刻 的用户空间(含该元素 CTM 之前的那套)。相机靠改 viewBox 时,用户单位下的端点跟着文档走,这通常是你要的。不要把 CSS 像素写进 userSpaceOnUse 还指望缩放后仍贴边。

2. Pattern 两套单位

patternUnits 管瓦片怎么铺到物体上;patternContentUnits 管 pattern 内部图形怎么解释。默认也是 objectBoundingBox,同样会在零尺寸盒上失败。

生产约定:

  • 图标/卡片的装饰底纹:objectBoundingBox,内容用 0..1。
  • 世界坐标网格、地图纹理:两边都 userSpaceOnUse,内容按用户单位画一格,用 patternTransform 只描述「一砖多大」,不要再乘相机 scale。相机是 viewBox,见 相机与锚点缩放

href 引用别的 pattern 做 inheriting,新代码同样不要 xlink:href

3. 滤镜会光栅化

filter 把作用对象(含后代)画进离屏缓冲再做 feGaussianBlur 等。输出是位图,放大后发糊、旋转后边缘锯齿,都是预期。

半径、stdDeviation滤镜区的用户空间 里。改 viewBox 放大「相机」时,同样的 blur(8) 打在更大缓冲上——和 Canvas filter 被 CTM 放大是同一类成本。

做法适用不适用
静态阴影预烘焙进 path / PNG图标、徽章每帧形状都变
局部 filter 在叶子上单个悬浮高亮整棵场景 &lt;g filter>
userSpaceOnUse 滤镜区要钉死在世界坐标的灯光跟着物体包围盒的倒影
导出时离线滤镜出 PNG/PDF交互热路径

filterUnits 默认 objectBoundingBox,区是盒外扩 x y width height(默认 -10% / 120%)。模糊被裁切:区太小。区太大:缓冲爆炸。零尺寸盒同样让滤镜区退化。

组上的 opacity 也会逼出离屏层,见 绘制顺序与层叠。滤镜 + 组透明度叠加接近相乘,生产里别叠。

4. 适用 / 不适用

适用:主题化图标渐变、小面积卡片阴影、导出时的一次性处理。
不适用:无限画布每帧全场景 blur、用滤镜模拟布尔挖洞、用 pattern 当瓦片引擎(节点数和重绘见 06 性能)。

5. 失败形态

症状原因
直线/细 rect 渐变消失objectBoundingBox 宽或高为 0
缩放后模糊核变成「糊全屏」滤镜半径 × 相机放大,滤镜打在巨大组上
砖块双重缩放patternTransform 又乘了 CSS scale / 相机
模糊被方框裁死默认滤镜区 120% 不够 stdDeviation
渐变跟着物体「滑出」混用了 userSpaceOnUse 端点与物体局部坐标

权威资料

核对日期:2026-08-26