SnapDOM v3 完全指南:用纯前端 API 把 DOM 捕获为 SVG、PNG、Canvas 与可复用结果
【免费下载链接】snapdomHigh-performance engine for capturing, modifying, and converting DOM elements into any format.项目地址: https://gitcode.com/GitHub_Trending/sn/snapdom
SnapDOM(@zumer/snapdom)是一个运行在浏览器中的界面捕获引擎:它把页面元素的已渲染 DOM 状态(含样式、字体、图片)冻结为可复用结果,再用核心 API 导出为 SVG、PNG、JPG、WebP、Canvas 或 Blob,用官方插件导出为自包含 HTML、结构化上下文、视觉 Agent 元素地图、PDF 与 GIF/视频录制。本文以仓库根目录的 README.md 为主线,结合 src/ 下的源码实现与测试用例,系统讲解安装、用法、核心选项、v3 新特性与 v2 迁移要点,帮助你在项目中直接落地"一键导出截图 / 卡片 / 看板"类需求。
快速开始:一行代码导出 PNG
SnapDOM 的全部能力都挂在snapdom这一个入口上。最基础的用法是捕获一个元素并直接得到图片:
import { snapdom } from '@zumer/snapdom'; const card = document.querySelector('#card'); const image = await snapdom.toPng(card); document.body.appendChild(image);snapdom.toPng(element, options)这类一步式快捷方法会"捕获 + 导出"一气呵成。更推荐的做法是先捕获一次、再多次导出:捕获结果是一个自包含的结果对象,同一份捕获可以反复导出为不同格式,即使源元素之后发生了变化,结果对象仍然保存着当初捕获的状态;只有再次调用snapdom(card)才会捕获它的新状态:
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' });从源码看,这个"结果对象"由 src/api/snapdom.js 中的buildResult构建:它把核心导出器(img/svg/canvas/blob/png/jpeg/webp/download)与插件通过defineExports声明的导出器合并成一张导出映射表,再为每个导出器动态生成toXxx()方法,因此result.to(name, options)可以按名字调用任意核心或插件导出器。相关行为由tests/api.snapdom.test.js、tests/api.exports.resolution.test.js 等测试固定。
入口与构建产物
- 公开入口定义在 src/index.js:
@zumer/snapdom与@zumer/snapdom/plugins指向同一份运行时(共享同一个插件注册表与缓存),不会出现"子路径插件对主入口不可见"的问题。 - 构建产物(见 package.json 与 README 的 Build outputs 表):
| 文件 | 用途 |
|---|---|
dist/snapdom.mjs | ES module,供 import 与打包器使用 |
dist/snapdom.js | script 标签引入,暴露window.snapdom |
types/snapdom.d.ts | TypeScript 类型声明 |
注意:项目没有 CommonJS 构建,且@zumer/snapdom的exports只暴露了import条件分支。
安装与引入方式
核心与官方插件使用匹配的主版本号:
npm i @zumer/snapdom@latest @zumer/snapdom-plugins@latest浏览器 script 标签直接引入:
<script src="https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.js"></script> <script> snapdom.toPng(document.querySelector('#card')).then(image => { document.body.appendChild(image); }); </script>ES module 从 CDN 引入(https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.mjs提供同一模块):
import { snapdom } from 'https://esm.sh/@zumer/snapdom@latest'; import { htmlExport } from 'https://esm.sh/@zumer/snapdom-plugins@latest/html-export';若要在当前仓库目录下用本地构建运行文档站:
npm install npm run compile npm run site选择输出格式
捕获结果上的导出方法一览(README 核心表格,结合 src/api/snapdom.js 的结果对象实现):
| 结果方法 | 返回 |
|---|---|
toPng()、toJpg()、toWebp() | HTMLImageElement |
toSvg() | 基于 SVG 的HTMLImageElement |
toCanvas() | HTMLCanvasElement |
toBlob() | SVGBlob(除非在捕获或导出时显式指定了格式) |
toRaw()/url | 捕获的 SVG data URL |
download() | 按所选格式下载文件 |
to(name, options?) | 按名字运行核心或插件导出器 |
细节与约定:
- 静态快捷方法
snapdom.toPng、snapdom.toCanvas、snapdom.download等定义在同一文件中;toJpeg()是toJpg()的别名;toImg()为兼容而保留,需要 SVG 图片时优先用toSvg()。 - 导出选项归一化在
normalizeExportOptions中完成:v3 的尺寸规则是width/height为绝对输出尺寸并优先于scale;JPG/WebP 在未设置背景色时自动补白(#ffffff),避免透明区域被编码成黑色。 - 从源码看,所有核心导出都读取渲染产物:当一次捕获因插件把阶段降低到
clone(未渲染)而停止时,result.url与各导出方法会抛出带有原因的错误(absentArtifactError),而不是返回 undefined。 result.meta暴露冻结的捕获几何信息(viewBox、目标尺寸等),用于把导出的图片覆盖回源界面的场景;result.warnings记录本次捕获的降级诊断(图片降级为占位符、画布钳制、Safari PNG 回退等)。
设置尺寸与内容:核心选项
典型配置示例
const result = await snapdom(card, { width: 800, dpr: 1, backgroundColor: '#ffffff', exclude: '.capture-ignore', excludeMode: 'remove' });width与height定义输出尺寸;只设置其中一个时保持宽高比。两者都未设置时由scale决定,dpr再乘以像素尺寸。所有选项的归一化集中在 src/core/context.js 的createContext中完成——默认值、别名与编译后的排除策略都只在这里决定。
常用选项速查表(README 核心表格)
| 常用选项 | 默认值 | 用途 |
|---|---|---|
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 修改等变化后强制刷新 |
从源码理解选项语义
exclude与filter相互独立:createContext会把exclude拆分为选择器列表与谓词列表(excludeSelectors/excludePredicates),并编译出shouldExclude策略:节点命中顺序是data-capture="exclude"属性 →exclude→filter,第一个命中即决定该节点的模式并停止求值。filter采用 v2 的真值规则:任何 falsy 返回都会把节点过滤掉。embedFonts: 'auto':只内嵌元素实际使用、且文档确实声明为 webfont 的字族;系统字体页面会跳过整个内嵌阶段。true/false为显式开关。Safari 环境下,snapdom.capture会先等待元素实际用到的字体就绪,并对已有的 GPU 后端<canvas>做一次读取式"预热"(见 src/api/snapdom.js 与tests/api.safari.memoPreparation.test.js)。invalidate: true:一次强制全量刷新并清除底层样式缓存。它必须在 burst 之外生效,因为样式 memo 是 epoch 作用域的——程序化 CSSOM 编辑(如sheet.insertRule())不会产生任何 DOM 变更信号,需要显式失效。clip:支持'viewport'(用户当前所见)或{x, y, width, height}页面坐标矩形。裁剪捕获会在样式内联前剪掉屏外子树,比整页捕获更快。canvas:复用调用方提供的 canvas 作为导出目标,循环捕获(如实时镜像)时避免每次整画布拷贝;源码用isTag判断而非instanceof,以兼容 iframe 文档中的 canvas(对应tests/core.context.test.js)。- 其他资源选项:
useProxy提供代理 URL 或模板;fallbackURL为失败图片提供替换;placeholders: false把跨域 iframe 从条纹占位改为不可见占位;localFonts/excludeFonts/fontStylesheetDomains/iconFonts控制字体嵌入细节(详见 FEATURES.md)。
插件:导出 HTML 与结构化上下文
官方插件独立发布为@zumer/snapdom-plugins,必须与核心主版本号匹配,并声明对 v3 核心的 peer 依赖;源码位于 packages/plugins/。
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();html-export返回冻结克隆(含捕获样式与字体)组成的 HTML 文档,可离线重新打开布局;context-export输出文本大纲或 JSON 树(结构、角色、可见文本、状态、可选边界)。agent-map输出带编号徽标的截图 + JSON 元素地图(Set-of-Mark 格式),供视觉 Agent 直接点击定位;pdf-image、gif-export、video-export分别导出图片型 PDF、GIF 动画与浏览器编码视频。- 同一插件系统支持叠加层、脱敏(
redact-inputs,核心默认只遮罩密码框)、滤镜、时间戳、文本替换与自定义导出器。局部插件覆盖同名全局插件,且局部优先执行。完整清单与选项表格见 packages/plugins/README.md,钩子规范见 PLUGIN_SPEC.md。
插件注册方式:
// 全局:对所有捕获生效 snapdom.plugins(filter({ preset: 'sepia' })); // 局部:仅对本次捕获生效,并覆盖全局同名插件 const result = await snapdom(element, { plugins: [filter({ preset: 'dramatic' })] });需要注意:filter(与exclude、excludeStyleProps、fallbackURL)以函数形式传入时,每次新捕获都会重新求值,以便读取当前应用状态;这会关闭该捕获的 memoization 与差分重捕获,轮询循环里用谓词会付出每次全量捕获的代价——能用选择器表达的规则请优先传选择器以保留复用。捕获类插件除非声明pure: true,否则会挂起 memoization;只有确定性钩子才能声明pure: true(时间戳、读取外部状态的回调必须每次重跑)。
从 HTML 字符串捕获
snapdom.fromString()可以把一段 HTML 字符串挂载到屏外、捕获后自动移除:
const result = await snapdom.fromString('<article>Hello</article>'); const image = await result.toPng();源码实现(src/api/snapdom.js 的fromString):字符串通过innerHTML进入真实文档,因此会被解析并激活为与手写标记相同的行为——<img onerror>之类的内联处理器会在调用方 origin 运行,且可能在挂载移除后继续运行。必须先对不可信 HTML 做净化(DOMPurify 或等价工具)再传入。相关契约由tests/api.fromString.test.js 固定。
捕获结果作为 WebGL 纹理
捕获结果可以直接喂给 WebGL 纹理,实现实时镜像、过渡动画等效果:
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中包含把导出的图片覆盖回源界面所需的捕获几何信息。
v3 新特性:自动 memo、字体自动内嵌与预捕获
README 的 "What's new in v3" 总结了 v3 的核心改进,对应源码如下:
- 符合条件的未变化捕获自动复用首次结果:这正是 src/core/burst.js 的 burst memoization——每个元素一个作用域
MutationObserver加上每个元素最近结果的缓存,重复捕获未变化的子树时立即返回。安全的小范围局部变化只重建受影响的子树(差分路径,见 src/core/diff.js),其他变化走全量捕获。其失效矩阵非常完整:DOM 变更、视频/canvas/iframe/SMIL 等帧源、<img>加载、字体加载、滚动、表单控件状态、:hover、外部选择器状态、窗口缩放、<head>CSS、CSS/WAAPI 动画、捕获中途的编辑等都有对应的观察或签名机制。State 存放在 WeakMap 与有界 64 项 LRU 中,逐出时会断开所有 observer 与 listener。 - 网络字体使用时自动内嵌:
embedFonts: 'auto'只内嵌元素实际用到的、文档声明的 webfont 字族;系统字体捕获跳过该工作(减少每次捕获约 30ms 的等待,WebKit 上实测)。 - 样式遍历避免冗余读取,按捕获隔离状态以支持并发捕获(如
__iconMatchers编译一次并显式传递,避免并发捕获互相读到对方的匹配器)。 - Safari 图片解码与绘制保留浏览器特定处理(绘制的验证阶梯、PNG 回退等)。
snapdom.preCapture()学习捕获意图:当一次捕获在某个控件的 press/click 事件的任务内启动时,它学习该控件;之后对该控件的 hover 或 focus 就提前准备这次捕获,真正的点击到来时命中 memo,只付出导出成本。
snapdom.preCapture(); button.onclick = () => snapdom.toPng(card);从 src/api/preCapture.js 的源码看,其原理是"链接预取"的翻译:捕获没有 href,因此目标是学出来的——在pointerdown/keydown(Enter/Space)/click任务中发起的捕获归属到按下的控件,随后对同一控件的pointerenter/focusin即触发预捕获。已知控件之外,页面上的第一次意图事件还会对可见视口做一次预热捕获并丢弃(实测让首屏元素的首次捕获从 3.3 倍稳态降到 1.1 倍)。没有参数、没有属性、没有任何后台工作:意图事件发生前什么都不运行。
- 图片压缩与资源缓存保持自动:内嵌栅格图按可见分辨率(display box × scale × dpr)降采样、保留原 codec、仅在更小时才采用;更大尺寸的导出可恢复捕获时的原始图片,但不会重新读取实时图片的更新版本。这些优化"不会让栅格化与图片编码免费"——量化数据见 BENCHMARKS.md。
双渲染引擎
SnapDOM 有两个渲染引擎:
- SVG(默认):克隆完成后经
foreignObject组装为 SVG data URL。引擎实现在 src/engines/svg.js,负责 base reset、bbox/出血计算、foreignObject组装与 SVG data URL 编码,还会把重复的内联样式"intern"为属性选择器规则以压缩体积(500 行表格场景下 SVG 从 1.6MB 降到更小、绘制从 52.6ms 降到 37.4ms,字节级相同的像素,见tests/engine.svg.intern.test.js)。 - html-in-canvas(实验性):用
engine: 'html-in-canvas'选择,通过浏览器原生 canvas API 绘制同一份捕获克隆。它需要支持 canvas-place-element 标志的兼容浏览器,且构建时必须用SNAPDOM_CANVAS_ENGINE=1编译;默认构建只含 SVG。不支持的捕获回退到 SVG。原生捕获成功时产生位图,因此url与toRaw()返回 PNG 而非序列化 SVG。详见 ARCHITECTURE.md。
await snapdom(card, { engine: 'html-in-canvas' });从 v2 迁移
v3 的主捕获模式仍是snapdom(element, options),但以下变更需要逐项核对(README 迁移表):
| v2 | v3 | 需要改什么 |
|---|---|---|
| 网络字体需显式开启 | embedFonts: 'auto' | 通常无需改动;仅当你确实想省略时才用false |
栅格宽高可被scale相乘 | width/height 优先于 scale | 传最终尺寸:width: 400而不是width: 200, scale: 2 |
burst选择重复 memo | 符合条件的捕获自动 memo;burst不再文档化/支持 | 删除burst(引擎内部仍读取,勿依赖);不可观察变化后用invalidate: true强制一次新鲜捕获 |
preCache预准备资源 | 已移除;preCapture()学习捕获意图 | 删除preCache;preCapture()不是简单的改名替换 |
fast选择优化路径 | 已移除 | 删除该选项 |
filter/filterMode与exclude/excludeMode可同时使用 | 两套控制及独立模式都保留;exclude还接受谓词 | 保留现有规则与模式;仅在需要时使用谓词形式 |
cache: 'auto'或'full' | 两者都映射为'soft' | 通常省略;'disabled'/false仅用于调试 |
compress控制图片降采样 | 图片优化自动进行;compress不再文档化/支持 | 删除compress(引擎内部仍读取,勿依赖) |
resolvePicturePlaceholders/pictureResolver配置懒图准备 | 响应式/懒加载图片在克隆上解析;选项不再文档化/支持 | 删除选项,在捕获前于应用内处理自定义加载/超时 |
| 部分可见输入值被脱敏 | 核心只遮罩密码框 | 其他字段用redactInputs() |
afterExport返回值成为下一个钩子的载荷 | 返回值被忽略;钩子接收同一导出载荷 | 停止链式返回;用defineExports产出不同输出 |
插件 v2 暴露@zumer/snapdom-plugins/html-in-canvas | 该子路径已移除 | 用实验性核心engine: 'html-in-canvas'(需兼容自定义构建);默认构建用 SVG |
TypeScript 导出PluginExportFacade | 具名类型已移除;ctx.exports仍提供核心导出器 | 在defineExports中推断,或用NonNullable<CaptureContext['exports']> |
filter 与 exclude 组合使用
两者是独立控制项:filter(node)返回 true 保留、false 过滤;filterMode控制被过滤节点对布局的影响。exclude用选择器或谓词补充排除;排除谓词返回 true 表示省略该节点;excludeMode控制这些省略。同一捕获中可以同时使用两套控制与不同模式:
// 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']),任一规则命中即排除。两种模式默认都是'hide'。判定顺序为data-capture="exclude"→exclude→filter,首个命中决定模式并停止求值;filter使用 v2 真值规则(任何 falsy 返回即过滤)。
读取变化应用状态的回调
函数型filter、exclude、excludeStyleProps、fallbackURL的捕获会每次新鲜运行,以便回调读取当前应用状态——它们不会复用未变化的捕获或早先回调的样式/回退决策;也不必因为回调闭包变化而使用invalidate。代价是函数型规则会关闭该捕获的 memoization 与差分重捕获:
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。
限制与边界
README 明确列出的限制,引用时必须注意:
- 需要浏览器 DOM:服务器端 Node.js 进程要运行它,必须有浏览器环境。
- 跨域资源:跨域图片、字体与样式表需要可读资源或合适的代理。
crossorigin属性本身不授予访问权,除非服务器也允许。跨域 iframe 使用占位。 - SVG 输出含
<foreignObject>内的 HTML:适合浏览器;其他 SVG 查看器与文档工具的兼容性因实现而异。 - 输出依赖浏览器渲染与 canvas 限制:Safari 在 WebP 编码不可用时可能回退到 PNG。
- 变化源:canvas、视频等变化表面每次捕获都是新鲜的;JavaScript CSSOM 编辑不可自动观察,之后用
invalidate: true。 - 脱敏边界:核心捕获可见输入值;语义插件会在文本/地图输出中脱敏敏感字段值,但附加的图片若也要隐藏像素,仍需
redactInputs或exclude。
详细技术特性与浏览器行为见 FEATURES.md。
性能基准与开发
- 量化测量(首捕获、重复捕获、图片密集场景分开统计)见 BENCHMARKS.md。做竞品对比时请使用相同的场景、输出格式、scale 与 DPR,并同时比较图片质量与耗时。
- 从本仓库构建与测试(package.json 脚本 + README Development 一节):
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:packnpm run site用本地构建提供文档站。npm test只检查 lint 不改文件;npm run lint:fix应用修复。
小结
SnapDOM v3 的核心心智模型是:一次捕获(snapdom(element, options)),任意导出(result.toXxx())。捕获阶段冻结 DOM 状态、快照样式、内嵌字体与图片并产出可复用结果;导出阶段按需把结果转成图片、canvas、Blob、HTML 或结构化数据。v3 在此基础上加入自动 memo 与差分重捕获、embedFonts: 'auto'、preCapture()意图预捕获,并把 v2 中需要显式开关的优化变为默认行为——迁移时只需删除旧选项、改用最终尺寸与invalidate即可。进一步深入可阅读 ARCHITECTURE.md(实现笔记)、FEATURES.md(技术特性)、PLUGIN_SPEC.md(插件契约)与 packages/plugins/README.md(官方插件参考)。
【免费下载链接】snapdomHigh-performance engine for capturing, modifying, and converting DOM elements into any format.项目地址: https://gitcode.com/GitHub_Trending/sn/snapdom
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考