优秀的编程知识分享平台

网站首页 > 技术文章 正文

snapDOM 为什么这么快?深度解析其性能优势

nanyue 2025-08-03 07:06:49 技术文章 1 ℃

snapDOM 为什么这么快?深度解析其性能优势

引言

在日常业务开发中,我们经常会用到DOM截图功能。业界也有不少开源的工具,例如,html2canvas。但是大部分DOM截图类的工具也都有一个致命的问题就是慢。还是以html2canvas举例,常规情况下生成一次图片大概耗时1s+,遇到大DOM,可能直接就卡成了假死状态,用户体验极差。

但是snapDOM 以其惊人的性能表现脱颖而出。根据基准测试,snapDOM 相比 html2canvas 快 32-133 倍,相比 modern-screenshot 快 2-93 倍


本文将深入分析 snapDOM 的技术架构,揭示其性能优势的根本原因。

核心性能优势

1. 技术路径的根本差异

snapDOM:DOM 序列化策略

// snapDOM 的核心流程
1. 深度克隆 DOM → 2. 内联样式 → 3. 内联资源 → 4. 生成 SVG → 5. 转换为 data URL

html2canvas:Canvas 重绘策略

// html2canvas 的核心流程
1. 解析 DOM 结构 → 2. 计算样式 → 3. 在 Canvas 上逐像素绘制 → 4. 处理每个元素

关键差异:snapDOM 是"数据转换",html2canvas 是"重新渲染"

2. 算法复杂度对比

snapDOM 的 O(n) 复杂度

// 每个 DOM 节点只处理一次
export function deepClone(node, styleMap, styleCache, nodeMap, compress) {
// 1. 克隆节点结构 - O(1)
const clone = node.cloneNode(false);
// 2. 内联样式 - O(1) 每个节点
inlineAllStyles(node, clone, styleMap, styleCache, compress);
// 3. 递归处理子节点 - O(n) 总节点数
node.childNodes.forEach((child) => {
const clonedChild = deepClone(
child,
styleMap,
styleCache,
nodeMap,
compress
);
});
}

html2canvas 的 O(n×m) 复杂度

// 每个 DOM 节点都需要在 Canvas 上重新绘制
// n = DOM 节点数,m = 每个节点的像素数
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// 计算每个像素的颜色值
ctx.fillStyle = calculatePixelColor(x, y);
ctx.fillRect(x, y, 1, 1);
}
}

关键技术优化

1. 多层缓存机制

样式缓存

const styleCache = new WeakMap();
if (!cache.has(source)) {
cache.set(source, getStyle(source));
}

图片缓存

const imageCache = new Map();
if (imageCache.has(src)) {
return Promise.resolve(imageCache.get(src));
}

背景图片缓存

const bgCache = new Map();
if (bgCache.has(encodedUrl)) {
return `url(${bgCache.get(encodedUrl)})`;
}

CSS 类缓存

const baseCSSCache = new Map();
if (baseCSSCache.has(tagKey)) {
baseCSS = baseCSSCache.get(tagKey);
}

2. 样式压缩优化

CSS 类生成

if (compress) {
const keyToClass = generateCSSClasses(styleMap);
classCSS = Array.from(keyToClass.entries())
.map(([key, className]) => `.${className}{${key}}`)
.join("");
// 应用 CSS 类而不是内联样式
for (const [node, key] of styleMap.entries()) {
const className = keyToClass.get(key);
if (className) node.classList.add(className);
}
}

优势

  • 相同样式只生成一次 CSS 类
  • 大幅减少 SVG 文件大小
  • 提高后续处理速度

3. 资源处理策略

批量异步处理

// 图片批量处理,避免阻塞
for (let i = 0; i < imgs.length; i += 4) {
const group = imgs.slice(i, i + 4).map(processImg);
await Promise.allSettled(group);
}
// 使用 idle 回调避免阻塞主线程
await new Promise((resolve) => {
idle(
async () => {
await inlineImages(clone, options);
resolve();
},
{ fast }
);
});

4. 内存使用优化

WeakMap 避免内存泄漏

const styleCache = new WeakMap(); // 自动垃圾回收

流式处理

const queue = [[source, clone]];
while (queue.length) {
const [srcNode, cloneNode] = queue.shift();
// 处理单个节点,避免一次性加载所有数据
}

及时清理

const sandbox = document.getElementById("snapdom-sandbox");
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();

输出格式优势

1. SVG 的文本生成优势

// SVG 是文本格式,生成速度快
const svgString = svgHeader + foString + svgFooter;
dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;

优势

  • SVG 生成是字符串操作,非常快
  • 不需要像素级计算
  • 文件大小通常更小

对比其他dom生成图片的工具,snapDOM是采用的将html转成svg之后绘制在dom上的方式。这样做有两个好处:

  • 可以通过直接内联css的方式来实现样式,而不需要每个dom都去获取computedStyle,降低性能开销
  • 免去了dom创建和渲染的过程

通过svg的foreignObject来引入html,是snapDOM一大很有创新性的技术特点。

2. 矢量输出特性

<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">
<!-- HTML 内容 -->
</div>
</foreignObject>
</svg>

特殊元素处理优化

1. Canvas 自动转换

if (node.tagName === "CANVAS") {
const dataURL = node.toDataURL();
const img = document.createElement("img");
img.src = dataURL;
img.width = node.width;
img.height = node.height;
return img; // 转换为图片元素
}

2. 伪元素内联

// 将 ::before, ::after 转换为真实 DOM 元素
for (const pseudo of ["::before", "::after", "::first-letter"]) {
const style = getStyle(source, pseudo);
if (hasContent || hasBg || hasBgColor) {
const pseudoEl = document.createElement("span");
pseudoEl.dataset.snapdomPseudo = pseudo;
// 应用样式并插入到克隆元素中
}
}

3. 排除元素处理

if (node.getAttribute("data-capture") === "exclude") {
const spacer = document.createElement("div");
spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
return spacer; // 快速跳过
}

性能数据对比

基准测试结果

场景

snapDOM vs html2canvas

snapDOM vs modern-screenshot

小元素 (200×100)

32.27× 更快

6.46× 更快

模态框 (400×300)

32.66× 更快

7.28× 更快

页面视图 (1200×800)

35.29× 更快

13.17× 更快

大滚动区域 (2000×1500)

68.85× 更快

38.23× 更快

超大元素 (4000×2000)

133.12× 更快

93.31× 更快

Tags:

最近发表
标签列表