Website NPM version NPM weekly downloads GitHub contributors GitHub stars GitHub forks Sponsor tinchox5 License

English | 简体中文

# SnapDOM SnapDOM 是面向 Web 界面的浏览器捕获引擎。它将渲染后的 DOM 状态连同样式、字体和图片保存为可复用的结果。 核心引擎可导出图片和 Canvas;插件可导出自包含的 HTML、页面上下文、供视觉智能体使用的元素映射、PDF 和录制内容。捕获结果也可用于 WebGL 纹理、视觉回归测试和界面过渡。整个过程在页面内运行,使用标准 Web API,核心引擎零依赖。 [文档与演示](https://snapdom.dev/) · [技术功能](FEATURES_CN.md) · [官方插件](packages/plugins/README.md) · [English](README.md) 本仓库介绍的是 **v3.x.x**。下方迁移指南以 **v2.x.x** 为比较基准。[v2 源码](https://github.com/zumerlab/snapdom/tree/v2)和 [v2 文档](https://snapdom.dev/v2/)仍可访问。 ## 可以用它做什么 | 用途 | 输出 | 提供方 | | --- | --- | --- | | 分享卡片、图表、发票或仪表盘 | SVG、PNG、JPG、WebP、Canvas 或 Blob | 核心引擎 | | 将捕获结果用于纹理、覆盖层或过渡效果 | Canvas 与捕获几何信息 | 核心引擎 | | 保存页面片段,供以后展示 | 包含捕获样式和字体的 HTML | `html-export` 插件 | | 为智能体或日志提供页面内容 | 文本/JSON 上下文,或附带元素映射的图片 | `context-export` / `agent-map` 插件 | | 下载文档或录制变化中的内容 | 基于图片的 PDF、动画 GIF 或浏览器编码的视频 | `pdf-image` / `gif-export` / `video-export` 插件 | 图片、HTML 和上下文导出使用捕获时保存的状态。GIF 和视频插件则在一段时间内持续捕获当前元素。 ## 快速开始 ```js import { snapdom } from '@zumer/snapdom'; const card = document.querySelector('#card'); const image = await snapdom.toPng(card); document.body.appendChild(image); ``` 需要多种输出时,捕获一次即可: ```js const result = await snapdom(card); const image = await result.toPng(); const canvas = await result.toCanvas(); const blob = await result.toBlob({ format: 'png' }); await result.download({ format: 'jpg', filename: 'card' }); ``` 即使源元素随后发生变化,结果对象仍保留这次捕获。再次调用 `snapdom(card)` 才会捕获新状态。 ## 安装 安装核心,并按需安装官方插件;两者的主版本号必须一致: ```sh npm i @zumer/snapdom@latest @zumer/snapdom-plugins@latest ``` 也可以在浏览器中加载: ```html ``` 以 ES Module 形式从 CDN 加载: ```js import { snapdom } from 'https://esm.sh/@zumer/snapdom@latest'; import { htmlExport } from 'https://esm.sh/@zumer/snapdom-plugins@latest/html-export'; ``` `https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.mjs` 提供同一份模块。这些示例加载核心和插件的最新发布版本。 如需用本仓库的本地构建运行文档站点: ```sh npm install npm run compile npm run site ``` 本地站点使用本地构建产物,官网演示则加载已发布的包。 ### 构建产物 | 文件 | 用途 | | --- | --- | | `dist/snapdom.mjs` | 用于导入和打包工具的 ES Module | | `dist/snapdom.js` | 通过 script 标签加载,提供 `window.snapdom` | | `types/snapdom.d.ts` | TypeScript 类型声明 | 没有 CommonJS 构建产物。`@zumer/snapdom/plugins` 与包的主入口共用同一份运行时和插件注册表。 ## 基本用法 ### 选择输出格式 | 结果对象的方法 | 返回 | | --- | --- | | `toPng()`、`toJpg()`、`toWebp()` | `HTMLImageElement` | | `toSvg()` | 以 SVG 为数据源的 `HTMLImageElement` | | `toCanvas()` | `HTMLCanvasElement` | | `toBlob()` | 若捕获或导出时未显式指定格式,则返回 SVG `Blob` | | `toRaw()` / `url` | 捕获结果的 SVG Data URL | | `download()` | 下载所选格式 | | `to(name, options?)` | 按名称调用核心或插件导出器 | `snapdom.toPng(element, options)` 等快捷方法在一次调用中完成捕获和导出。结果对象还提供 `toJpeg()`,作为 `toJpg()` 的别名。`toImg()` 仍然可用;导出 SVG 图片时建议使用 `toSvg()`。 ### 设置尺寸与内容 ```js const result = await snapdom(card, { width: 800, dpr: 1, backgroundColor: '#ffffff', exclude: '.capture-ignore', excludeMode: 'remove' }); ``` `width` 和 `height` 指定输出尺寸;只设置一个时保持宽高比。只有两者都未设置时才应用 `scale`,最终像素尺寸还会乘以 `dpr`。 | 常用选项 | 默认值 | 用途 | | --- | --- | --- | | `scale` / `dpr` | `1` / 设备像素比 | 输出分辨率 | | `width` / `height` | 未设置 | 输出尺寸 | | `embedFonts` | `'auto'` | 嵌入捕获内容实际使用的网页字体 | | `backgroundColor` | 透明;JPG/WebP 为白色 | 输出背景 | | `exclude` | 无 | 选择器或判断函数;`true` 表示排除 | | `excludeMode` | `'hide'` | 保留不可见的占位空间,或设为 `'remove'` | | `filter` | 无 | 判断函数;`true` 保留节点,`false` 过滤节点 | | `filterMode` | `'hide'` | 独立控制被 `filter` 过滤节点的布局方式 | | `clip` | 未设置 | 捕获视口或页面坐标下的矩形区域 | | `captureSelection` | `false` | 包含用户的文字选区 | | `canvas` | 未设置 | 复用现有 Canvas | | `invalidate` | `false` | 在编程修改 CSSOM 等变化后刷新捕获 | | `fast` | `true` | 设为 `false` 时,长时间捕获期间页面仍可响应 | [完整选项](https://snapdom.dev/docs/options/)还包括阴影、变换、字体、CORS、回退方案和布局校正。 ### 导出超长页面 使用默认的 SVG 引擎时,PNG 文件导出会自动处理超出浏览器 Canvas 限制的捕获。捕获一次,即可下载一个完整分辨率的文件,或获取它的 Blob: ```js const capture = await snapdom(article); await capture.download({ format: 'png', scale: 2 }); // 或自行上传、保存文件: const blob = await capture.toBlob({ format: 'png', scale: 2, dpr: 1 }); ``` SnapDOM 将保存的捕获结果分块渲染,再编码为一个 PNG 文件。它保留请求的尺寸,包括阴影和留白,无需为整张图片分配一个 Canvas。`download()` 始终使用 DPR 1;`toBlob()` 使用捕获时的 DPR,除非导出时另行指定。未超出限制的导出仍使用浏览器原生编码器。 此功能需要 `CompressionStream`;不支持它的旧浏览器仍会缩小输出并发出警告。超大文件的编码仍需要时间和内存,打开这些文件的应用也有自己的图片限制。 ### 分段导出超长页面 `toCanvas()`、`toPng()` 和 JPEG/WebP 导出仍使用单个 Canvas,超出 SnapDOM 的尺寸保护阈值时会整体缩小(Safari 每边 16,384px,Chrome/Firefox 每边 32,767px,另有总像素面积限制)。需要分别获取多个 Canvas 时,可以使用 `crop`: ```js const capture = await snapdom(article); const { contentX, contentY, w0, h0 } = capture.meta; const pieceHeight = 4000; // CSS px for (let y = 0; y < h0; y += pieceHeight) { const canvas = await capture.toCanvas({ scale: 2, dpr: 1, crop: { x: contentX, y: contentY + y, width: w0, height: Math.min(pieceHeight, h0 - y) } }); // 保存或上传这一段,然后继续 } ``` `crop` 是一个矩形,不是开关。它使用 `capture.meta` 的坐标,每次调用返回一个 canvas。捕获只执行一次,重复的只是栅格化。请让 `pieceHeight × scale × dpr` 保持在上限以内,并显式设置 `dpr`,因为它默认等于设备像素比。[分块原理](https://snapdom.dev/blog/huge-page-mosaic/)。 ### 导出 HTML 或结构化上下文 官方插件单独发布为 `@zumer/snapdom-plugins`,主版本号必须与核心一致;插件声明了对 v3 核心的 peer 依赖。插件源码位于本仓库的 `packages/plugins/`。 ```js import { htmlExport, contextExport } from '@zumer/snapdom-plugins'; const result = await snapdom(card, { plugins: [htmlExport(), contextExport({ format: 'json' })] }); const html = await result.toHtml(); const context = await result.toContext(); ``` 同一套插件系统也支持覆盖层、内容遮蔽和自定义导出器。同名的局部插件优先于全局插件。参见[官方插件参考](packages/plugins/README.md)和[插件规范](PLUGIN_SPEC.md)。 ### 捕获 HTML 字符串 ```js const result = await snapdom.fromString('
Hello
'); const image = await result.toPng(); ``` `fromString()` 在屏幕外挂载 HTML,并在捕获后移除。这段字符串会像你自己编写的页面标记一样被解析和激活:`` 之类的内联事件处理器会在调用方的源(origin)中执行,挂载被移除后也可能继续执行。不受信任的 HTML 请先清理,例如使用 DOMPurify。 ### 用于 WebGL 纹理 ```js const canvas = document.createElement('canvas'); const texture = new THREE.CanvasTexture(canvas); texture.colorSpace = THREE.SRGBColorSpace; async function refresh(element) { await snapdom.toCanvas(element, { canvas, scale: 1, dpr: 1 }); texture.needsUpdate = true; } ``` `result.meta` 包含捕获几何信息,可用于将导出的图片覆盖到原界面上。[实验示例](https://snapdom.dev/labs.html)展示了纹理、镜像和过渡效果。 ## v3 有哪些新变化 - 符合条件且未变化的捕获会复用第一次的结果。可安全处理的局部变化只重建受影响的子树,其他变化使用完整捕获流程。 - 使用到的网页字体会自动嵌入;纯系统字体的捕获会跳过这一步。 - 样式处理减少了重复读取,每次捕获的状态也相互隔离,以支持并发捕获。 - Safari 图片解码和绘制保留了针对该浏览器的处理。 - `snapdom.preCapture()` 可根据用户意图提前准备捕获。当捕获在控件的按下/点击事件中开始时,它会学习这个控件;之后悬停或获得焦点时,就会提前准备同一次捕获。 ```js snapdom.preCapture(); button.onclick = () => snapdom.toPng(card); ``` 图片压缩和资源缓存仍会自动处理,但光栅化与图片编码依然需要时间。参见[性能测量](BENCHMARKS.md)和[缓存指南](https://snapdom.dev/docs/cache/)。 SnapDOM 有两个渲染引擎:默认的 **SVG**,以及通过浏览器原生 Canvas API 绘制同一份捕获克隆的 **html-in-canvas**。使用 `engine: 'html-in-canvas'` 选择第二个引擎。 第二个引擎仍处于实验阶段:需要兼容的浏览器开启 Canvas 绘制标志,并使用 `SNAPDOM_CANVAS_ENGINE=1` 编译构建。默认构建只包含 SVG。不支持的捕获会回退到 SVG。成功的原生捕获生成位图,因此其 URL 和 `toRaw()` 返回 PNG,而非序列化的 SVG。详情见 [ARCHITECTURE.md](ARCHITECTURE.md)。 ## 从 v2 迁移 主要调用方式仍是 `snapdom(element, options)`。本指南以 **v2.x.x** 为迁移基准。升级前请检查以下变化: | v2 | v3 | 需要调整的地方 | | --- | --- | --- | | 网页字体需要手动开启嵌入 | `embedFonts: 'auto'` | 通常无需调整;仅在需要省略字体时使用 `false` | | 位图的 width/height 可能再乘以 `scale` | width/height 优先于 scale | 直接传最终尺寸,如用 `width: 400` 替代 `width: 200, scale: 2` | | 使用 `burst` 手动开启重复捕获记忆化 | 符合条件的捕获自动记忆化;`burst` 不再记录在文档中,也不再受支持 | 删除 `burst`(引擎仍会读取它供内部使用,请勿依赖),并在 `sheet.insertRule()` 等无法自动观察的变化后,用 `invalidate: true` 重新捕获一次 | | `preCache` 用于准备资源 | 已移除;`preCapture()` 学习捕获意图 | 删除 `preCache`;`preCapture()` 不是直接改名后的替代方法 | | `fast: false` 通过空闲回调进行克隆 | `fast: false` 约每帧让出一次主线程,总耗时基本不变 | 保留 `fast: false` | | `filter` / `filterMode` 可与 `exclude` / `excludeMode` 同时使用 | 两种控制及其独立模式仍受支持;`exclude` 还支持判断函数 | 保留原有规则和模式;需要时再使用新增判断函数形式 | | `cache: 'auto'` 或 `'full'` | 两者均映射为 `'soft'` | 通常可以省略;`'disabled'` / `false` 用于调试 | | `compress` 控制内嵌图片降采样 | 图片优化自动进行;`compress` 不再记录在文档中,也不再受支持 | 删除 `compress`;引擎仍会读取它供内部使用,请勿依赖 | | `resolvePicturePlaceholders` / `pictureResolver` 配置懒加载图片预处理 | 在克隆节点上解析响应式和懒加载图片;这些选项不再记录在文档中,也不再受支持 | 删除这些选项,自定义加载和超时策略应在应用中、捕获前完成;引擎仍会读取 `resolvePicturePlaceholders` 供内部使用,请勿依赖 | | 部分可见输入值会被遮蔽 | 核心只遮蔽密码 | 其他字段需要使用 `redactInputs()` | | `afterExport` 返回值成为下一个钩子的参数,但不改变调用者收到的结果 | 返回值被忽略;钩子收到同一份导出参数 | 不再通过返回值串联钩子;用 `defineExports` 生成不同输出 | | 插件 v2.x.x 提供 `@zumer/snapdom-plugins/html-in-canvas` | 该子路径已移除 | 使用兼容的自定义构建和核心的实验性 `engine: 'html-in-canvas'`;默认构建使用 SVG | | TypeScript 导出 `PluginExportFacade` | 该类型名称已移除;`ctx.exports` 仍提供核心导出器 | 在 `defineExports` 中使用类型推导,或使用 `NonNullable` | ### 同时使用 filter 和 exclude 与 v2 一样,`filter` 和 `exclude` 是独立的控制项。`filter(node)` 返回 true 表示保留节点,返回 false 表示过滤掉节点;`filterMode` 决定被过滤节点如何影响布局。`exclude` 用选择器或判断函数指定额外排除的节点;其判断函数返回 true 表示排除。`excludeMode` 决定这些排除如何影响布局。一次捕获中可以同时使用两个控制项,并为它们设置不同模式: ```js // v2 和 v3 均支持:隐藏私有字段,移除工具栏。 await snapdom(card, { filter: node => !node.matches('[data-private]'), filterMode: 'hide', exclude: ['.toolbar'], excludeMode: 'remove' }); ``` `'hide'` 保留不可见的占位空间;`'remove'` 删除节点并允许剩余内容重新排版。两者都会排除节点内容。v3 还允许 `exclude` 混合选择器和判断函数,例如 `exclude: ['.toolbar', node => node.dataset.export === 'omit']`,任意规则匹配即可排除节点。这是可选的扩展,不会替代 `filter`,也不会合并两个模式。单独提供 CSS 效果的 `filter` 插件仍然可用。 两个模式都默认为 `'hide'`。对每个节点,先检查 `data-capture="exclude"`,再检查 `exclude`,最后检查 `filter`。首个排除决定所用模式,并停止对该节点继续检查:即使 `filter` 也会以不同模式过滤该节点,只要先匹配 `exclude`,就使用 `excludeMode`。`filter` 保留 v2 的真值规则:任何假值返回都会过滤节点。 ### 读取应用动态状态的回调 当 `filter`、`exclude`、`excludeStyleProps` 或 `fallbackURL` 使用函数时,每次新的捕获都会重新执行所需流程,让适用的回调读取当前应用状态,不复用旧捕获或先前回调的样式、替代图片决策。仅回调闭包状态改变时,无需使用 `invalidate`。这种即时性有代价:`filter`、`exclude`、`excludeStyleProps` 或 `fallbackURL` 使用函数时,该捕获会关闭记忆化和差异化重捕获,因此带判断函数的轮询循环每一轮都要付出一次完整捕获的开销。规则能用选择器表达时,请传选择器,以保留复用: ```js let privateMode = false; const options = { exclude: node => privateMode && node.matches('[data-private]'), excludeMode: 'remove' }; const before = await snapdom(card, options); privateMode = true; const after = await snapdom(card, options); // 使用当前策略 ``` `before` 仍保留原来的捕获状态;再次导出它不会应用新策略。请使用 `after` 这样的新捕获。节点排除和样式属性判断函数应保持同步并返回布尔值。直接修改 CSSOM 等无法自动观察的变化,仍需要 `invalidate: true`。 会影响捕获结果的插件暂停自动记忆化,除非声明 `pure: true`。只有确定性钩子才应这样声明;时间戳和读取外部状态的回调需要再次运行。参见 [v3 插件契约](PLUGIN_SPEC.md)。 ## 限制 - SnapDOM 需要浏览器 DOM。服务端 Node.js 进程需要浏览器环境才能运行它。 - 跨源图片、字体和样式表必须可读取,或通过适当的代理访问。仅设置 `crossorigin` 不会获得权限,服务器也必须允许访问。跨源 iframe 使用占位框。 - SVG 输出通过 `` 包含 HTML,适合在浏览器中展示;其他 SVG 查看器和文档工具的支持程度各不相同。 - 输出受浏览器渲染和图片尺寸限制影响;超长 PNG 文件的导出方式见[导出超长页面](#导出超长页面)。Safari 无法编码 WebP 时可能回退为 PNG。 - Canvas、视频和其他持续变化的内容会重新捕获。JavaScript 对 CSSOM 的修改无法被自动观察,修改后请使用 `invalidate: true`。 - 核心会捕获可见输入值。语义插件会在文本/映射输出中遮蔽敏感字段值,但如需同时隐藏附带图片中的这些像素,仍需使用 `redactInputs` 或 `exclude`。 详细行为见[技术功能与浏览器处理](FEATURES_CN.md)。 ## 性能基准测试 [测量记录](BENCHMARKS.md)分别介绍首次捕获、重复捕获和图片密集场景。[在线对比](https://snapdom.dev/compare/live/)会在你的浏览器中运行,并标明所加载的包版本。 比较时应使用相同场景、输出格式、scale 和 DPR,同时检查图像和耗时。 ## 文档 - [v2 文档归档](https://snapdom.dev/v2/)与 [v2 源码](https://github.com/zumerlab/snapdom/tree/v2) - [API](https://snapdom.dev/docs/api/)与[选项](https://snapdom.dev/docs/options/) - [框架指南](https://snapdom.dev/guides/)与[使用示例](https://snapdom.dev/how-to/) - [官方插件](packages/plugins/README.md)、[插件规范](PLUGIN_SPEC.md)与[插件贡献指南](CONTRIBUTING_PLUGINS.md) - [架构](ARCHITECTURE.md)与[技术功能](FEATURES_CN.md) ## 开发 在本仓库中运行: ```sh npm install npx playwright install npm run compile npm run lint npm run test:types npm run test:bundle BROWSER=all npx vitest run __tests__ --browser.headless npm run test:pack ``` `npm run site` 使用本地构建产物提供文档站点。`npm test` 检查 lint,但不会修改文件;如需自动修复,请运行 `npm run lint:fix`。实现说明见 [ARCHITECTURE.md](ARCHITECTURE.md)。 ## 贡献者

tinchox5 claude pdufour FlavioLimaMindera Jarvis2018 tarwin Amyuan23 kohaiy airamhr9 jswhisperer K1ender mosuzi ninecc 00200200 17biubiu av01d CHOYSEN pedrocateexte domialex stypr jamalkamaladdin mon-jai puneetdixit200 RexSkz RinZ27 sharuzzaman simon1uo titoBouzout

## 赞助者 特别感谢 [@megaphonecolin](https://github.com/megaphonecolin)、[@sdraper69](https://github.com/sdraper69)、[@reynaldichernando](https://github.com/reynaldichernando)、[@gamma-app](https://github.com/gamma-app)、[@jrjohnson](https://github.com/jrjohnson) 和 [@ryanander](https://github.com/ryanander) 对本项目的支持! 如果你也愿意支持这个项目,可以[成为赞助者](https://github.com/sponsors/tinchox5)。 ## 支持我们 如果 SnapDOM 帮你节省了时间,欢迎在 GitHub 上点一个星标,让更多开发者发现它。 用 SnapDOM 构建了项目?欢迎把这个徽章添加到你的 README: [![Built with SnapDOM](https://img.shields.io/badge/built%20with-SnapDOM-blue)](https://snapdom.dev) ```md [![Built with SnapDOM](https://img.shields.io/badge/built%20with-SnapDOM-blue)](https://snapdom.dev) ``` ### 使用 SnapDOM 的项目 以下项目正在使用 SnapDOM: - [LobeHub](https://github.com/lobehub/lobehub) — AI 智能体平台 - [Hugging Face Chat UI](https://github.com/huggingface/chat-ui) — HuggingChat 界面,使用 SnapDOM 截取作品预览 - [Sealos](https://github.com/labring/sealos) — AI 原生云操作系统 - [Tencent tmagic-editor](https://github.com/Tencent/tmagic-editor) — 低代码页面编辑器 - [Playroom](https://github.com/seek-oss/playroom) — SEEK 推出的 JSX 设计工具 - [GPT-Vis](https://github.com/antvis/GPT-Vis) — 蚂蚁集团 AntV 推出的、面向 AI 的数据可视化工具 - [Rabby Wallet](https://github.com/RabbyHub/Rabby) — 面向 EVM 链的浏览器钱包 - [uMap](https://github.com/umap-project/umap) — OpenStreetMap 地图制作工具 - [ListenBrainz](https://github.com/metabrainz/listenbrainz-server) — MetaBrainz 推出的音乐收听记录服务 - [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) — 思维导图核心库,推荐使用 SnapDOM 导出图片 - [Kong UI Components](https://github.com/Kong/public-ui-components) — Kong 仪表盘渲染器使用 SnapDOM 导出 PDF - [SnapDIFF](https://zumerlab.com/snapdiff/) — 浏览器内的视觉回归测试工具 *(由 Zumerlab 开发)* 完整案例见 **[snapdom.dev/made-with](https://snapdom.dev/made-with/)**。如果你的项目也在使用 SnapDOM,欢迎[提交 PR](https://github.com/zumerlab/snapdom/pulls)添加到列表中,仅收录真实、可验证的项目。 ## 许可证 MIT © Zumerlab