跳到主要内容

DOM节点成本与虚拟化

每个图形元素都要走样式、布局(SVG 几何)、命中和绘制。几百个带事件的 path 没问题;几千个独立节点,平移相机时 Recalculate Style + Paint 会先于「矢量很轻」的直觉把帧预算吃完。

1. 适用 / 不适用

适用: 流程图、地图标注、白板对象、图表标记——对象有 id、要选中、数量随视口变化。

不适用: 用虚拟化解决「每个点一份滤镜」;在已经合并的静态底图上再套一层复杂 <g> 动画。粒子 / 笔刷 / 热力图从一开始就该看 选型

2. 成本在树上,不在「path 字符串很长」

一条复杂 d 的光栅成本在 paint;五千条简单 path 的成本在 DOM。常见爆炸:

  • 每个数据点一个 <g> + circle + title
  • 每条边一个 path 还带 markerfilter
  • 把图标 <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