snapDOM——新一代网页截图 PDF 生成方案

· 7635 字符 · 3 分钟阅读
目录
snapDOM——新一代网页截图 PDF 生成方案

在前端开发里,“DOM 截图 → 图片/文件导出” 是一个高频需求场景。无论是营销海报、报表导出、还是社交分享卡片,截图工具的体验直接决定了用户感受。

传统方案如 html2canvas 功能强大,但它基于纯 JavaScript 模拟浏览器渲染,性能和体验常常差强人意。而 snapdom 的出现,借助浏览器原生能力,让截图变得更快、更轻、更稳定。本文将系统介绍 snapdom,并结合 PDF 生成给出实用方案。


一、兼容性:支持哪些浏览器和手机端?

snapdom 的底层依赖 SVG 的 <foreignObject> 特性,该特性可以让 SVG 原生渲染 HTML 片段。因此它的兼容性大致如下:

兼容性
兼容性

📌 总结:只要是现代浏览器,手机和 PC 上都能用;唯一的硬伤是 IE 浏览器无法支持


二、snapdom 是如何实现截图的?核心 API 与最小实现

实现原理

snapdom 的思路和 html2canvas 完全不同:

  • html2canvas:遍历 DOM → 解析每个节点样式 → 用 Canvas API 重绘一遍 → 输出图片。
  • snapdom:直接利用浏览器 → 克隆 DOM → 包进 <foreignObject> → SVG 渲染 → 转换为图片。

换句话说,snapdom 不再自己重造渲染引擎,而是让浏览器自己完成渲染。

核心 API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import snapdom from '@zumer/snapdom';

// 把 DOM 转成 PNG 图片
const imgUrl = await snapdom.toPng(document.querySelector('#target'));

// 触发下载
await snapdom.download(document.querySelector('#target'), {
  format: 'png',
  filename: 'screenshot.png',
});

最小实现(源码核心思想)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function miniSnap(target) {
  const svg = `
    <svg xmlns="http://www.w3.org/2000/svg" width="${target.offsetWidth}" height="${target.offsetHeight}">
      <foreignObject width="100%" height="100%">
        ${new XMLSerializer().serializeToString(target)}
      </foreignObject>
    </svg>
  `;
  const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
  const url = URL.createObjectURL(blob);

  const img = new Image();
  img.src = url;
  await img.decode();

  const canvas = document.createElement('canvas');
  canvas.width = target.offsetWidth;
  canvas.height = target.offsetHeight;
  canvas.getContext('2d').drawImage(img, 0, 0);

  return canvas.toDataURL('image/png');
}

三、snapdom 是如何处理样式的?

在克隆 DOM 的时候,snapdom 会做两件事:

  1. 内联样式化

    • 把 CSS 文件、字体、背景图、渐变等资源转成 inline(base64 或 dataURL)。
    • 确保克隆后的 DOM 是完全自包含的,不依赖外部资源。
  2. 复制页面样式表

    • 把当前页面所有可访问的样式表收集进 <style> 标签,注入 SVG 内部,保证截图和原页面一致。
    • 对跨域 CSS 文件会自动过滤,避免报错。

这样处理后,即便在无网络环境下(比如 App WebView 内),截图也能完全复现。


四、性能:为什么比 Canvas 高?最小对比示例

html2canvas 的逻辑:

1
2
3
4
// 遍历 DOM,每个节点用 Canvas API 绘制
context.drawImage(element, x, y, w, h);
context.fillText("Hello", ...);
// ...重复 N 次

问题:DOM 一复杂就成千上万次绘制调用,CPU 压力爆炸。

snapdom 的逻辑:

1
2
3
4
5
<svg xmlns="http://www.w3.org/2000/svg">
  <foreignObject width="100%" height="100%">
    <div> ...原始DOM... </div>
  </foreignObject>
</svg>

由浏览器引擎一次性排版+渲染,性能极高。
在官方测试中,snapdom 相比 html2canvas 快 30~100 倍,截图延迟从秒级降低到百毫秒级。


五、如何结合 jsPDF 生成 PDF?

基本使用

1
2
3
4
5
6
7
8
9
import { jsPDF } from 'jspdf';
import snapdom from '@zumer/snapdom';

async function exportPdf(target) {
  const imgUrl = await snapdom.toPng(target);
  const pdf = new jsPDF('p', 'pt', 'a4');
  pdf.addImage(imgUrl, 'PNG', 0, 0, 595, 842); // A4 尺寸
  pdf.save('document.pdf');
}

如果只是简单地生成 PDF,上述方法已经足够。但在实际业务场景中,HTML 转 PDF 远不止是将页面转为图像,还涉及分页、表格渲染、图片处理等复杂逻辑。
幸运的是,snapDOM 已经为我们处理了大部分基础问题,我们当前的主要任务是实现分页。

分页逻辑

当页面内容较少时,可以直接通过 snapDOM 对整个目标元素截图,再判断元素位置是否超出 A4 纸张边界,若超出则进行分页。

然而无论是 html2canvas 还是 snapDOM,这种方式都存在隐患:浏览器中的 Canvas 存在宽高限制,一旦超出限制,渲染结果可能会失败。
因此,更安全的方式是按照页面大小分块截图并拼接生成 PDF

对于打印场景,最合适的分块单位就是 一张 A4 纸的尺寸(当然,你也可以根据业务需求自定义块大小)。

但如果手动去实现分页与分块,逻辑会非常繁琐。这里 pagedjs 就能发挥作用:

例如,下图展示的页面无需手动计算分页,只需在 HTML 中添加几行代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<script>
  window.PagedConfig = {
    auto: false,
    after: (flow) => {
      console.log('after', flow);
    },
  };

  setTimeout(() => {
    window.PagedPolyfill.preview();
  }, 1000);
</script>

添加后即可自动获取分页结果:

  • 未分页的效果:

    待转换的网页
    未分页

  • 完成分页的效果:

    分完页的网页
    已分页

从 DOM 结构上看,pagedjs 会生成清晰的分页层级:

html结构
html结构

截图并生成 PDF

分页完成后,我们可以遍历所有 pagedjs_page 元素,逐页截图并写入 jsPDF:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const pdf = new jsPDF('p', 'pt', 'a4');

const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();

const pages = document.getElementsByClassName('pagedjs_page');

for (let i = 0; i < pages.length; i++) {
  const page = pages[i];
  const img = await snapdom.toJpg(page, {
    width: pageWidth,
    height: pageHeight,
    quality: 0.8,
    dpr: 1,
  });

  if (i > 0) {
    pdf.addPage();
  }

  pdf.addImage(img, 'JPEG', 0, 0, pageWidth, pageHeight);
}

pdf.save('demo.pdf');

最终效果

这样,我们就能顺利完成 PDF 生成。与 html2canvas 相比:

  • snapDOM 渲染速度更快
  • 生成的 PDF 清晰度更高
  • 对于 MathJax 公式,snapDOM 也能正确渲染

👉 查看生成效果
(注:示例中的序号未能正确渲染,可能与样式处理有关)

相关扩展

  • snapDOM 官方已有计划支持 直接生成 PDF(以插件形式),可关注讨论:Future Idea: PDFs
  • html2pdf 也计划集成 snapDOM 支持,详情可见:html2pdf.js Issue

六、总结与拓展

  • 兼容性:支持所有现代浏览器(含手机端),唯一缺陷是不支持 IE。
  • 实现方式:DOM → foreignObject → SVG → 图片,核心 API 极简。
  • 样式处理:自动 inline 化,保证截图完全复现。
  • 性能优势:避免重造轮子,直接利用浏览器渲染引擎,比 Canvas 快几十倍。
  • 扩展能力:可与 jsPDF 等库无缝结合,实现 PDF 导出。

📌 额外优势:

  • 包体积轻(约 20KB,比 html2canvas 小 80%)
  • API 简单,一行代码就能完成截图/下载
  • 适合 H5、小程序、Electron 等现代环境

一句话总结:
snapdom = 极速截图 + PDF 导出 的最佳组合。如果你还在为 html2canvas 的慢和卡顿发愁,不妨试试 snapdom 🚀。