滤镜合成层与重绘
filter、mask、clipPath 以及组上的 opacity / mix-blend-mode,都会把子树先画到 离屏缓冲 再贴回。这是合成规则,不是「特效免费」。节点成本见 DOM 节点成本;组级 opacity 的颜色语义见 绘制顺序。
1. 适用 / 不适用
适用: 少量装饰(一枚图标的软阴影、选中高光);导出前烘焙;静态图层上的一次性滤镜。
不适用: 每个数据点一份 feGaussianBlur;在会频繁改 viewBox 的相机层上挂全图滤镜;用 will-change: filter 常驻「优化」。
2. 什么会逼出离屏
| 触发 | 发生什么 |
|---|---|
<g filter="url(#blur)"> | 子树光栅进缓冲,按滤镜原语处理 |
mask / 非简单 clipPath | 先出 mask 缓冲再合成 |
组 opacity < 1 | 先合成组,再整体淡出 |
mix-blend-mode | 隔离 + 与背景混合 |
isolation: isolate | 明确建组 |
叶子上的 opacity 通常比组上便宜,语义也不同。模糊半径按 用户空间 解释,再光栅;缓冲分辨率由引擎挑,不是 无限矢量。
export function attachBlur(
svg: SVGSVGElement,
target: SVGElement,
stdDeviation: number,
): string {
const doc = svg.ownerDocument;
const ns = "http://www.w3.org/2000/svg";
let defs = svg.querySelector("defs");
if (!defs) {
defs = doc.createElementNS(ns, "defs");
svg.prepend(defs);
}
const id = `blur-${crypto.randomUUID()}`;
const filter = doc.createElementNS(ns, "filter");
filter.id = id;
const blur = doc.createElementNS(ns, "feGaussianBlur");
blur.setAttribute("stdDeviation", String(stdDeviation));
filter.append(blur);
defs.append(filter);
target.setAttribute("filter", `url(#${id})`);
return id;
}
stdDeviation 稍大,离屏矩形就膨胀。动画中改偏差等于每帧重光栅。生产装饰能预渲染成 <image> 就不要实时滤镜。
3. 缩放后发糊
相机改 viewBox 或 CSS 盒子变大之后,旧滤镜缓冲被拉伸,发光边缘发糊、阴影成块。CSS transform: scale 整棵 SVG 更糟:命中、线宽、滤镜分辨率一起坏,见 viewport。
对策:
- 相机层不要挂全场景
filter - 必须滤镜时,缩放稳定后再上,或按缩放重建 filter 原语
will-change: transform同样可能把 SVG 提升成位图层,动画结束后去掉,见 CSS 与 WAAPIvector-effect: non-scaling-stroke只管线宽,不管滤镜清晰度
4. 怎么测
不要猜。Chrome:
- Performance:勾选 Rendering,看 Paint 是否被 filter 拉长。
- Layers:彩色层边框;滤镜组常是独立层。层多、层大,滚动/缩放必卡。
- 对比:去掉
filter/ 把组opacity改到叶子,同一相机手势跑一遍。
export function measureStylePaint(label: string, mutate: () => void): void {
performance.mark(`${label}-start`);
mutate();
performance.mark(`${label}-end`);
performance.measure(label, `${label}-start`, `${label}-end`);
}
这段只覆盖同步 JS。滤镜光栅看 Performance 里的 Paint / Update Layer Tree,不要用 measure 的毫秒当帧预算结论。
Safari 大 blur 更易卡;Android WebView 不要用桌面 Chrome 层数类推。
5. 失败形态
| 症状 | 原因 |
|---|---|
| 平移还行,一加阴影掉帧 | 每节点或整组实时 blur |
| 放大后光晕成位图块 | 离屏缓冲未按新分辨率重建 |
| 两个半透明圆重叠色不对 | opacity 写在 <g> 上先合成 |
| Layers 里几十块全屏 | will-change / filter / 组 opacity 喷在容器上 |
| 导出 PDF 滤镜丢失或更糊 | 打印链路不走浏览器那套滤镜光栅 |
权威资料
核对日期:2026-08-26