DOM节点成本与虚拟化
每个图形元素都要走样式、布局(SVG 几何)、命中和绘制。几百个带事件的 path 没问题;几千个独立节点,平移相机时 Recalculate Style + Paint 会先于「矢量很轻」的直觉把帧预算吃完。
1. 适用 / 不适用
适用: 流程图、地图标注、白板对象、图表标记——对象有 id、要选中、数量随视口变化。
不适用: 用虚拟化解决「每个点一份滤镜」;在已经合并的静态底图上再套一层复杂 <g> 动画。粒子 / 笔刷 / 热力图从一开始就该看 选型。
2. 成本在树上,不在「path 字符串很长」
一条复杂 d 的光栅成本在 paint;五千条简单 path 的成本在 DOM。常见爆炸:
- 每个数据点一个
<g>+circle+title - 每条边一个
path还带marker和filter - 把图标
<use>成万次却每个再包可点热区
静态、永不命中的底图应 合并 d(或少量按图层合并),而不是「结构清晰」地保留五千个叶子。
export function mergeStaticPaths(ds: readonly string[]): string {
return ds.join(" ");
}
export function paintMerged(
parent: SVGGElement,
d: string,
fill: string,
): SVGPathElement {
const path = parent.ownerDocument.createElementNS(
"http://www.w3.org/2000/svg",
"path",
);
path.setAttribute("d", d);
path.setAttribute("fill", fill);
path.setAttribute("pointer-events", "none");
parent.append(path);
return path;
}
合并后失去单对象命中。需要点选的仍保留独立节点,或命中走空间索引自己算,见 03。
3. 按视口挂载
相机用 viewBox 时,用户空间视口是已知矩形。只把相交节点插入图层,离开则 remove()。display: none 仍可能留在文档、占内存;真要省,从树上拿掉。
export interface SpatialItem {
id: string;
minX: number;
minY: number;
maxX: number;
maxY: number;
mount: (parent: SVGGElement) => SVGElement;
}
export function syncViewport(
layer: SVGGElement,
items: readonly SpatialItem[],
view: { minX: number; minY: number; width: number; height: number },
mounted: Map<string, SVGElement>,
): void {
const x1 = view.minX + view.width;
const y1 = view.minY + view.height;
const visible = new Set<string>();
for (const item of items) {
const hit =
item.maxX >= view.minX &&
item.minX <= x1 &&
item.maxY >= view.minY &&
item.minY <= y1;
if (!hit) continue;
visible.add(item.id);
if (!mounted.has(item.id)) {
mounted.set(item.id, item.mount(layer));
}
}
for (const [id, node] of mounted) {
if (!visible.has(id)) {
node.remove();
mounted.delete(id);
}
}
}
包围盒用用户空间 AABB。描边很宽时盒要膨胀,不要信跨引擎的 getBBox({ stroke: true }),见 差异清单。
预取一圈 margin,避免平移时节点闪现。不要每帧全量 innerHTML 重建。
4. 何时改 Canvas
| 信号 | 判断 |
|---|---|
| 可见对象经常 > 2k,且大多不可点 | 底图 Canvas 或合并 path,交互层留 SVG |
每帧改大量 d / 顶点 | 立即模式(Canvas / WebGL) |
| 笔刷、橡皮、像素滤镜烘焙 | Canvas |
| 要 DOM 无障碍 + 对象语义 | 留下 SVG,虚拟化 |
混合很常见:静态栅格/Canvas 底图 + SVG 标注。不要在 SVG 里用五千个半透明 circle 模拟热力。
5. 失败形态
| 症状 | 原因 |
|---|---|
| 空闲也 40fps | 节点全挂载,平移触发全树 paint |
| 虚拟化后闪烁 | 无 margin,或用 display:none 当卸载 |
| 合并后点不准 | 可点对象被烤进一张 path |
| 换成 Canvas 仍卡 | 每帧还在读回像素或全量 blit,问题在光栅管线不在 SVG 节点 |
| 内存涨 | 卸载没 remove,Map 里幽灵节点 |
权威资料
核对日期:2026-08-26