@zumer/snapdom 2.24.15 โ 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -246
- package/dist/snapdom.js +35 -10
- package/dist/snapdom.mjs +35 -10
- package/package.json +20 -14
- package/types/snapdom.d.ts +339 -165
- package/dist/plugins.mjs +0 -7
- package/dist/preCache.mjs +0 -10
package/README.md
CHANGED
|
@@ -36,325 +36,300 @@
|
|
|
36
36
|
|
|
37
37
|
# SnapDOM
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
It converts any DOM subtree into a self-contained representation that can be exported to SVG, PNG, JPG, WebP, Canvas, Blob, or **any custom format** through plugins. Ultra-fast, modular, extensible, and dependency-free.
|
|
39
|
+
SnapDOM is a browser capture engine for web interfaces. It captures rendered DOM state as a reusable result, with styles, fonts and images included.
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
Export images and canvas with the core. Use plugins for self-contained HTML, page context, maps for visual agents, PDF and recordings. Captures can also feed WebGL textures, visual regression tests and UI transitions. Everything runs in the page, using standard Web APIs, with no core dependencies.
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
[Documentation and demos](https://snapdom.dev/) ยท [Technical features](FEATURES.md) ยท [Official plugins](packages/plugins/README.md) ยท [็ฎไฝไธญๆ](README_CN.md)
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
This checkout documents **v3**. The migration guide below compares it with 2.24.16, the last v2 release.
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
## What you can build
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
| Use | Output | Provided by |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Share a card, chart, invoice or dashboard | SVG, PNG, JPG, WebP, canvas or Blob | Core |
|
|
52
|
+
| Reuse a capture in a texture, overlay or transition | Canvas plus capture geometry | Core |
|
|
53
|
+
| Save a page fragment for later display | HTML with captured styles and fonts | `html-export` plugin |
|
|
54
|
+
| Give an agent or a log a view of page content | Text/JSON context, or an image with an element map | `context-export` / `agent-map` plugins |
|
|
55
|
+
| Download a document or record changing content | Image-based PDF, animated GIF or browser-encoded video | `pdf-image` / `gif-export` / `video-export` plugins |
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
Image, HTML and context exports use the captured state. GIF and video plugins record the live element over time.
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
## Quick Start
|
|
56
|
-
|
|
57
|
-
**Capture any DOM element to PNG in one line:**
|
|
59
|
+
## Quick start
|
|
58
60
|
|
|
59
61
|
```js
|
|
60
62
|
import { snapdom } from '@zumer/snapdom';
|
|
61
63
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
+
const card = document.querySelector('#card');
|
|
65
|
+
const image = await snapdom.toPng(card);
|
|
66
|
+
document.body.appendChild(image);
|
|
64
67
|
```
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
Capture once when you need several outputs:
|
|
67
70
|
|
|
68
71
|
```js
|
|
69
|
-
const result = await snapdom(
|
|
70
|
-
|
|
71
|
-
await result.
|
|
72
|
-
await result.
|
|
72
|
+
const result = await snapdom(card);
|
|
73
|
+
|
|
74
|
+
const image = await result.toPng();
|
|
75
|
+
const canvas = await result.toCanvas();
|
|
76
|
+
const blob = await result.toBlob({ format: 'png' });
|
|
77
|
+
await result.download({ format: 'jpg', filename: 'card' });
|
|
73
78
|
```
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
## Table of Contents
|
|
78
|
-
|
|
79
|
-
- [Quick Start](#quick-start)
|
|
80
|
-
- [Features](#features)
|
|
81
|
-
- [Website & Live Demos](#website--live-demos)
|
|
82
|
-
- [Installation](#installation)
|
|
83
|
-
- [Build Outputs](#build-outputs)
|
|
84
|
-
- [Usage](#usage)
|
|
85
|
-
- [Documentation](#documentation): full API, Options, Plugins & Cache reference on [snapdom.dev/docs](https://snapdom.dev/docs/)
|
|
86
|
-
- [Limitations](#limitations)
|
|
87
|
-
- [Performance Benchmarks](#performance-benchmarks)
|
|
88
|
-
- [Development](#development)
|
|
89
|
-
- [Contributors](#contributors)
|
|
90
|
-
- [Sponsors](#sponsors)
|
|
91
|
-
- [Show your support](#show-your-support)
|
|
92
|
-
- [License](#license)
|
|
80
|
+
The result keeps that capture even if the source element later changes. Call `snapdom(card)` again to capture its new state.
|
|
93
81
|
|
|
94
82
|
## Installation
|
|
95
83
|
|
|
96
|
-
|
|
84
|
+
Install the core and, when you need them, the official plugins; use matching major versions:
|
|
97
85
|
|
|
98
|
-
```
|
|
99
|
-
npm i @zumer/snapdom
|
|
100
|
-
yarn add @zumer/snapdom
|
|
86
|
+
```sh
|
|
87
|
+
npm i @zumer/snapdom @zumer/snapdom-plugins
|
|
101
88
|
```
|
|
102
89
|
|
|
103
|
-
|
|
90
|
+
Or load it in a browser:
|
|
104
91
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
92
|
+
```html
|
|
93
|
+
<script src="https://unpkg.com/@zumer/snapdom/dist/snapdom.js"></script>
|
|
94
|
+
<script>
|
|
95
|
+
snapdom.toPng(document.querySelector('#card')).then(image => {
|
|
96
|
+
document.body.appendChild(image);
|
|
97
|
+
});
|
|
98
|
+
</script>
|
|
110
99
|
```
|
|
111
100
|
|
|
112
|
-
|
|
101
|
+
As an ES module from a CDN:
|
|
113
102
|
|
|
103
|
+
```js
|
|
104
|
+
import { snapdom } from 'https://esm.sh/@zumer/snapdom';
|
|
105
|
+
import { htmlExport } from 'https://esm.sh/@zumer/snapdom-plugins/html-export';
|
|
106
|
+
```
|
|
114
107
|
|
|
115
|
-
|
|
108
|
+
`https://unpkg.com/@zumer/snapdom/dist/snapdom.mjs` serves the same module. Pin package versions in production.
|
|
116
109
|
|
|
117
|
-
|
|
118
|
-
<!-- Minified build -->
|
|
119
|
-
<script src="https://unpkg.com/@zumer/snapdom/dist/snapdom.js"></script>
|
|
110
|
+
To run the docs site against a local build of this checkout instead:
|
|
120
111
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
```sh
|
|
113
|
+
npm install
|
|
114
|
+
npm run compile
|
|
115
|
+
npm run site
|
|
125
116
|
```
|
|
126
117
|
|
|
127
|
-
|
|
118
|
+
The local site runs the local build. The public site's demos load the published package.
|
|
128
119
|
|
|
129
|
-
|
|
130
|
-
<!-- Minified build (dev) -->
|
|
131
|
-
<script src="https://unpkg.com/@zumer/snapdom@dev/dist/snapdom.js"></script>
|
|
120
|
+
### Build outputs
|
|
132
121
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
122
|
+
| File | Use |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `dist/snapdom.mjs` | ES module for imports and bundlers |
|
|
125
|
+
| `dist/snapdom.js` | Script tag exposing `window.snapdom` |
|
|
126
|
+
| `types/snapdom.d.ts` | TypeScript declarations |
|
|
138
127
|
|
|
139
|
-
|
|
128
|
+
There is no CommonJS build. `@zumer/snapdom/plugins` and the package root share the same runtime and plugin registry.
|
|
140
129
|
|
|
141
|
-
|
|
142
|
-
|---------|------|----------|
|
|
143
|
-
| **ESM** (tree-shakeable) | `dist/snapdom.mjs` | Bundlers (Vite, webpack), `import` |
|
|
144
|
-
| **IIFE** (global) | `dist/snapdom.js` | Script tag, legacy `require` |
|
|
130
|
+
## Usage
|
|
145
131
|
|
|
146
|
-
|
|
147
|
-
```js
|
|
148
|
-
import { snapdom } from '@zumer/snapdom'; // โ dist/snapdom.mjs
|
|
149
|
-
```
|
|
132
|
+
### Choose an output
|
|
150
133
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
134
|
+
| Result method | Returns |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `toPng()`, `toJpg()`, `toWebp()` | An `HTMLImageElement` |
|
|
137
|
+
| `toSvg()` | An SVG-backed `HTMLImageElement` |
|
|
138
|
+
| `toCanvas()` | An `HTMLCanvasElement` |
|
|
139
|
+
| `toBlob()` | An SVG `Blob` unless a format was explicitly set on the capture or export |
|
|
140
|
+
| `toRaw()` / `url` | The capture's SVG data URL |
|
|
141
|
+
| `download()` | Downloads the chosen format |
|
|
142
|
+
| `to(name, options?)` | Runs a core or plugin exporter by name |
|
|
156
143
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
```
|
|
144
|
+
One-step shortcuts such as `snapdom.toPng(element, options)` capture and export in one call. Results also have `toJpeg()` as an alias for `toJpg()`. `toImg()` remains available; prefer `toSvg()` for an SVG image.
|
|
145
|
+
|
|
146
|
+
### Set size and content
|
|
161
147
|
|
|
162
|
-
**Official plugins** live in their own package:
|
|
163
|
-
```bash
|
|
164
|
-
npm install @zumer/snapdom-plugins
|
|
165
|
-
```
|
|
166
148
|
```js
|
|
167
|
-
|
|
149
|
+
const result = await snapdom(card, {
|
|
150
|
+
width: 800,
|
|
151
|
+
dpr: 1,
|
|
152
|
+
backgroundColor: '#ffffff',
|
|
153
|
+
exclude: '.capture-ignore',
|
|
154
|
+
excludeMode: 'remove'
|
|
155
|
+
});
|
|
168
156
|
```
|
|
169
157
|
|
|
158
|
+
`width` and `height` define output size. If only one is set, the aspect ratio is preserved. `scale` applies when neither is set, and `dpr` multiplies the pixel dimensions.
|
|
170
159
|
|
|
171
|
-
|
|
160
|
+
| Common option | Default | Purpose |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| `scale` / `dpr` | `1` / device pixel ratio | Output resolution |
|
|
163
|
+
| `width` / `height` | Unset | Output dimensions |
|
|
164
|
+
| `embedFonts` | `'auto'` | Embed the web fonts the capture uses |
|
|
165
|
+
| `backgroundColor` | Transparent; white for JPG/WebP | Output background |
|
|
166
|
+
| `exclude` | None | Selectors or predicates; `true` means exclude |
|
|
167
|
+
| `excludeMode` | `'hide'` | Keep an invisible spacer, or use `'remove'` |
|
|
168
|
+
| `filter` | None | Predicate; `true` keeps a node and `false` filters it out |
|
|
169
|
+
| `filterMode` | `'hide'` | Independent layout mode for nodes rejected by `filter` |
|
|
170
|
+
| `clip` | Unset | Capture the viewport or a page-coordinate rectangle |
|
|
171
|
+
| `captureSelection` | `false` | Include the user's text selection |
|
|
172
|
+
| `canvas` | Unset | Reuse an existing canvas |
|
|
173
|
+
| `invalidate` | `false` | Refresh after changes such as programmatic CSSOM edits |
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
|---------|-------------|
|
|
175
|
-
| **Reusable** `snapdom(el)` | One clone โ many exports (PNG + JPG + download). |
|
|
176
|
-
| **Shortcuts** `snapdom.toPng(el)` | Single export, less code. |
|
|
175
|
+
[All options](https://snapdom.dev/docs/options/) include shadows, transforms, fonts, CORS, fallbacks and layout reconciliation.
|
|
177
176
|
|
|
178
|
-
###
|
|
177
|
+
### Export HTML or structured context
|
|
179
178
|
|
|
180
|
-
|
|
179
|
+
Official plugins are published separately as `@zumer/snapdom-plugins` and must match the core major version; they declare a peer dependency on a v3 core. Their sources live in `packages/plugins/` in this checkout.
|
|
181
180
|
|
|
182
181
|
```js
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
import { htmlExport, contextExport } from '@zumer/snapdom-plugins';
|
|
183
|
+
|
|
184
|
+
const result = await snapdom(card, {
|
|
185
|
+
plugins: [htmlExport(), contextExport({ format: 'json' })]
|
|
186
|
+
});
|
|
185
187
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
await result.download({ format: 'jpg', filename: 'my-capture.jpg' });
|
|
188
|
+
const html = await result.toHtml();
|
|
189
|
+
const context = await result.toContext();
|
|
189
190
|
```
|
|
190
191
|
|
|
191
|
-
|
|
192
|
+
The same plugin system supports overlays, redaction and custom exporters. Local plugins override global plugins by name. See the [official plugin reference](packages/plugins/README.md) and [plugin specification](PLUGIN_SPEC.md).
|
|
192
193
|
|
|
193
|
-
|
|
194
|
+
### Capture HTML strings
|
|
194
195
|
|
|
195
196
|
```js
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
document.body.appendChild(png);
|
|
197
|
+
const result = await snapdom.fromString('<article>Hello</article>');
|
|
198
|
+
const image = await result.toPng();
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
`fromString()` mounts the markup offscreen and removes it after capture. The string is parsed and activated like markup you wrote yourself: inline handlers such as `<img onerror>` run in the caller's origin, and can keep running after the mount is removed. Sanitize untrusted HTML first, with DOMPurify or equivalent.
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
### Feed a WebGL texture
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
```js
|
|
206
|
+
const canvas = document.createElement('canvas');
|
|
207
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
208
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
209
|
+
|
|
210
|
+
async function refresh(element) {
|
|
211
|
+
await snapdom.toCanvas(element, { canvas, scale: 1, dpr: 1 });
|
|
212
|
+
texture.needsUpdate = true;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
206
215
|
|
|
207
|
-
|
|
208
|
-
- SnapDOM relies on Canvas, which enforces strict CORS policies. The browser's rendering engine is more permissive for onโscreen display.
|
|
216
|
+
`result.meta` contains the capture geometry needed to place an exported image over the source interface. The [labs](https://snapdom.dev/labs.html) show textures, mirrors and transitions.
|
|
209
217
|
|
|
210
|
-
|
|
218
|
+
## What's new in v3
|
|
211
219
|
|
|
212
|
-
|
|
220
|
+
- Eligible unchanged captures reuse the first result. Safe local changes rebuild only affected subtrees; other changes use a full capture.
|
|
221
|
+
- Web fonts embed automatically when used. System-font captures skip that work.
|
|
222
|
+
- The style pass avoids redundant reads, and per-capture state is isolated for concurrent captures.
|
|
223
|
+
- Safari image decoding and drawing retain their browser-specific handling.
|
|
224
|
+
- `snapdom.preCapture()` can prepare captures on user intent. It learns a control when a capture starts during its press/click event, then prepares that capture on later hover or focus.
|
|
213
225
|
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
|
|
218
|
-
crossorigin="anonymous"
|
|
219
|
-
/>
|
|
226
|
+
```js
|
|
227
|
+
snapdom.preCapture();
|
|
228
|
+
button.onclick = () => snapdom.toPng(card);
|
|
220
229
|
```
|
|
221
230
|
|
|
222
|
-
|
|
231
|
+
Image compression and resource caching remain automatic. They do not make rasterization or image encoding free. See [performance measurements](BENCHMARKS.md) and the [cache guide](https://snapdom.dev/docs/cache/).
|
|
223
232
|
|
|
224
|
-
|
|
233
|
+
SnapDOM has two rendering engines: **SVG**, the default, and **html-in-canvas**, which paints the same captured clone through the browser's native canvas API. Select the second with `engine: 'html-in-canvas'`.
|
|
225
234
|
|
|
226
|
-
The
|
|
227
|
-
|
|
228
|
-
- **[API reference](https://snapdom.dev/docs/api/)**: the `snapdom()` reusable object, shortcut methods, and exporter-specific options.
|
|
229
|
-
- **[Options](https://snapdom.dev/docs/options/)**: every capture option (`scale`, `dpr`, `embedFonts`, `useProxy`, `exclude`/`filter`, `compress`, `outerTransforms`, `outerShadows`, `cache`โฆ) explained with examples.
|
|
230
|
-
- **[Plugins](https://snapdom.dev/docs/plugins/)**: build, register and ship custom plugins and export formats. Browse community plugins on the [plugins page](https://snapdom.dev/plugins.html).
|
|
231
|
-
- **[Cache & preCache](https://snapdom.dev/docs/cache/)**: control caching between captures and preload resources.
|
|
232
|
-
|
|
233
|
-
Popular guides: **[convert HTML to PNG](https://snapdom.dev/how-to/html-to-png/)** ยท **[tile a full-page capture](https://snapdom.dev/blog/huge-page-mosaic/)** ยท **[run SnapDOM from Playwright or Puppeteer](https://snapdom.dev/blog/dom-capture-boundaries/)** ยท **[how SnapDOM captures the DOM](https://snapdom.dev/blog/painting-without-canvas/)**
|
|
234
|
-
|
|
235
|
-
### API at a glance
|
|
236
|
-
|
|
237
|
-
`snapdom(el, options?)` returns a reusable object (`toPng`, `toSvg`, `toCanvas`, `toBlob`, `toJpg`, `toWebp`, `download`, `url`). For single exports, use the shortcuts:
|
|
238
|
-
|
|
239
|
-
| Method | Description |
|
|
240
|
-
| ------------------------------ | --------------------------------- |
|
|
241
|
-
| `snapdom.toSvg(el, options?)` | Returns an SVG `HTMLImageElement` |
|
|
242
|
-
| `snapdom.toCanvas(el, options?)` | Returns a `Canvas` |
|
|
243
|
-
| `snapdom.toBlob(el, options?)` | Returns an SVG or raster `Blob` |
|
|
244
|
-
| `snapdom.toPng(el, options?)` | Returns a PNG image |
|
|
245
|
-
| `snapdom.toJpg(el, options?)` | Returns a JPG image |
|
|
246
|
-
| `snapdom.toWebp(el, options?)` | Returns a WebP image |
|
|
247
|
-
| `snapdom.download(el, options?)` | Triggers a download |
|
|
248
|
-
|
|
249
|
-
### Options at a glance
|
|
250
|
-
|
|
251
|
-
All options are optional and can be passed to `snapdom(el, options)` or any shortcut method.
|
|
252
|
-
|
|
253
|
-
| Option | Type | Default | Description |
|
|
254
|
-
| ------ | ---- | ------- | ----------- |
|
|
255
|
-
| `scale` | `number` | `1` | Output scale multiplier |
|
|
256
|
-
| `dpr` | `number` | `devicePixelRatio` | Pixel density of the rasterized output |
|
|
257
|
-
| `width` / `height` | `number` | `null` | Target output size (keeps aspect ratio if only one is set) |
|
|
258
|
-
| `backgroundColor` | `string` | `null` (`#ffffff` for JPEG/WebP) | Background fill |
|
|
259
|
-
| `quality` | `number` | `0.92` | JPEG/WebP quality (0โ1) |
|
|
260
|
-
| `format` | `'png' \| 'jpeg' \| 'webp' \| 'svg'` | `'png'` | Format for `download()` |
|
|
261
|
-
| `type` | `string` | `'svg'` | Blob type for `toBlob()` (`'png'`, `'jpeg'`โฆ) |
|
|
262
|
-
| `filename` | `string` | `'snapDOM'` | Download filename |
|
|
263
|
-
| `embedFonts` | `boolean` | `false` | Inline `@font-face` so text renders with your real fonts |
|
|
264
|
-
| `iconFonts` | `string \| RegExp \| array` | `[]` | Icon font families (always embedded) |
|
|
265
|
-
| `localFonts` | `array` | `[]` | Explicit fonts: `{ family, src, weight?, style? }` |
|
|
266
|
-
| `excludeFonts` | `object` | none | Skip fonts by family / domain / subset |
|
|
267
|
-
| `exclude` | `string[]` | `[]` | CSS selectors to leave out of the capture |
|
|
268
|
-
| `filter` | `(el) => boolean` | `null` | Keep-predicate (return `false` to drop a node) |
|
|
269
|
-
| `excludeMode` / `filterMode` | `'hide' \| 'remove'` | `'hide'` | How excluded nodes are handled |
|
|
270
|
-
| `clip` | `'viewport' \| {x, y, width, height}` | `null` | Capture only a region; offscreen content is pruned |
|
|
271
|
-
| `compress` | `boolean` | `true` | Downsample inlined images to their visible resolution |
|
|
272
|
-
| `useProxy` | `string` | `''` | CORS proxy prefix for cross-origin images |
|
|
273
|
-
| `fallbackURL` | `string \| fn` | none | Fallback image for broken `<img>` |
|
|
274
|
-
| `cache` | `'soft' \| 'auto' \| 'full' \| 'disabled'` | `'soft'` | Cache policy between captures |
|
|
275
|
-
| `outerTransforms` | `boolean` | `true` | Keep root translate/rotate in the output |
|
|
276
|
-
| `outerShadows` | `boolean` | `false` | Expand bounds to include root shadows/blur/outline |
|
|
277
|
-
| `fast` | `boolean` | `true` | Skip idle delays for faster capture |
|
|
278
|
-
| `reconcile` | `boolean` | `false` | Measure the clone against the live DOM and pin any diverging box to its real size. Fixes rare text re-wrap/layout drift at the cost of roughly doubling capture time. snapdom warns once (`console.warn`) if it detects a capture that could benefit from it |
|
|
279
|
-
| `burst` | `boolean` | `false` | Memoizes repeated captures of this element via a scoped MutationObserver, so an unchanged repeat skips the pipeline entirely. Without it, snapdom warns once if the same element is captured 3+ times within 2s |
|
|
280
|
-
| `invalidate` | `boolean` | `false` | With `burst: true`, forces a fresh capture for changes automatic tracking can't see (canvas draws, programmatic CSSOM edits) |
|
|
281
|
-
| `plugins` | `array` | none | Per-capture plugins (override globals by name) |
|
|
282
|
-
|
|
283
|
-
๐ **[Full API & every option, explained with examples โ snapdom.dev/docs](https://snapdom.dev/docs/)**
|
|
235
|
+
The second engine is experimental: it still needs a compatible browser with its canvas drawing flag enabled, plus a build compiled with `SNAPDOM_CANVAS_ENGINE=1`. The default build includes SVG only. Unsupported captures fall back to SVG. A successful native capture produces a bitmap, so its URL and `toRaw()` return PNG, not serialized SVG. See [ARCHITECTURE.md](ARCHITECTURE.md) for details.
|
|
284
236
|
|
|
285
|
-
##
|
|
237
|
+
## Migrating from v2
|
|
286
238
|
|
|
287
|
-
|
|
288
|
-
* When WebP format is used on Safari, it will fallback to PNG rendering (verified on Safari 26.5: `canvas.toDataURL('image/webp')` returns PNG). `download()` keeps the `.webp` filename, so the saved file carries PNG bytes.
|
|
289
|
-
* `@font-face` CSS rule is well supported. Fonts registered from JavaScript with `FontFace()` are *not* embedded automatically: list them in the `localFonts` option (`{ family, src }`), or use the workaround in [`#43`](https://github.com/zumerlab/snapdom/issues/43)
|
|
290
|
-
* **Safari**: captures with `embedFonts` or background/mask images run slower due to [WebKit #219770](https://bugs.webkit.org/show_bug.cgi?id=219770) (font decode timing). SnapDOM waits for the fonts the element actually uses and verifies the first canvas draw, so there is nothing to configure.
|
|
291
|
-
* **Custom scrollbar styles** (`::-webkit-scrollbar`): Applied only when the element has *not* been scrolled. When scrolled, the viewport content is captured without the scrollbar.
|
|
239
|
+
The main capture pattern remains `snapdom(element, options)`. This guide compares v3 with **v2.24.16**; review these changes before upgrading:
|
|
292
240
|
|
|
241
|
+
| In v2 | In v3 | What to change |
|
|
242
|
+
| --- | --- | --- |
|
|
243
|
+
| Web fonts were opt-in | `embedFonts: 'auto'` | Usually nothing; use `false` only if you want to omit them |
|
|
244
|
+
| Raster width/height could be multiplied by `scale` | Width/height win over scale | Pass the final size: `width: 400` instead of `width: 200, scale: 2` |
|
|
245
|
+
| `burst` opted into repeat memoization | Eligible captures memoize automatically; `burst` is no longer documented or supported | Remove `burst` (the engine still reads it for internal use, do not rely on it) and use `invalidate: true` for one fresh capture after unobservable changes such as `sheet.insertRule()` |
|
|
246
|
+
| `preCache` prepared resources | Removed; `preCapture()` learns capture intent | Remove `preCache`; `preCapture()` is not a drop-in rename |
|
|
247
|
+
| `fast` selected an optimization path | Removed | Delete the option |
|
|
248
|
+
| `filter` / `filterMode` and `exclude` / `excludeMode` could be used together | Both controls and their independent modes remain supported; `exclude` also accepts predicates | Keep existing rules and modes; use the additional predicate form only when useful |
|
|
249
|
+
| `cache: 'auto'` or `'full'` | Both map to `'soft'` | Usually omit it; `'disabled'` / `false` is for debugging |
|
|
250
|
+
| `compress` controlled embedded image downsampling | Image optimization is automatic; `compress` is no longer documented or supported | Remove `compress`; the engine still reads it for internal use, do not rely on it |
|
|
251
|
+
| `resolvePicturePlaceholders` / `pictureResolver` configured lazy-image preparation | Responsive/lazy image resolution happens on the clone; these options are no longer documented or supported | Remove the options and handle custom loading/timeouts in your app before capture; the engine still reads `resolvePicturePlaceholders` for internal use, do not rely on it |
|
|
252
|
+
| Some visible input values were redacted | Core masks passwords only | Add `redactInputs()` for other fields |
|
|
253
|
+
| `afterExport` returns became the next hook's payload, not the caller's result | Returns are ignored; hooks receive the same export payload | Stop chaining through return values; use `defineExports` to produce a different output |
|
|
254
|
+
| TypeScript exported `PluginExportFacade` | The named type is removed; `ctx.exports` still provides core exporters | Infer it in `defineExports`, or use `NonNullable<CaptureContext['exports']>` |
|
|
293
255
|
|
|
294
|
-
|
|
256
|
+
### Use filter and exclude together
|
|
295
257
|
|
|
296
|
-
|
|
297
|
-
Values are **average capture time (ms)** โ lower is better.
|
|
258
|
+
`filter` and `exclude` are independent controls, as in v2. `filter(node)` returns true to keep a node and false to filter it out; `filterMode` controls how a filtered node affects layout. `exclude` specifies additional omissions using selectors or predicates; an exclusion predicate returns true to omit the node. `excludeMode` controls those omissions. You can keep both controls and different modes in the same capture:
|
|
298
259
|
|
|
299
|
-
|
|
260
|
+
```js
|
|
261
|
+
// Works in v2 and v3: hide private fields, remove the toolbar.
|
|
262
|
+
await snapdom(card, {
|
|
263
|
+
filter: node => !node.matches('[data-private]'),
|
|
264
|
+
filterMode: 'hide',
|
|
265
|
+
exclude: ['.toolbar'],
|
|
266
|
+
excludeMode: 'remove'
|
|
267
|
+
});
|
|
268
|
+
```
|
|
300
269
|
|
|
301
|
-
|
|
302
|
-
| ------------------------ | --------------- | -------------- | ----------- | ------------- |
|
|
303
|
-
| Small (200ร100) | **0.5 ms** | 0.8 ms | 67.7 ms | 3.1 ms |
|
|
304
|
-
| Modal (400ร300) | **0.5 ms** | 0.8 ms | 75.5 ms | 3.6 ms |
|
|
305
|
-
| Page View (1200ร800) | **0.5 ms** | 0.8 ms | 114.2 ms | 3.3 ms |
|
|
306
|
-
| Large Scroll (2000ร1500) | **0.5 ms** | 0.8 ms | 186.3 ms | 3.2 ms |
|
|
307
|
-
| Very Large (4000ร2000) | **0.5 ms** | 0.9 ms | 425.9 ms | 3.3 ms |
|
|
270
|
+
`'hide'` keeps an invisible spacer; `'remove'` drops the node and allows reflow. Both omit its content. V3 additionally lets `exclude` mix selectors and predicates, such as `exclude: ['.toolbar', node => node.dataset.export === 'omit']`; any matching rule excludes the node. This is optional and does not replace `filter` or merge the two modes. The separate CSS-effect plugin named `filter` is also available.
|
|
308
271
|
|
|
272
|
+
Both modes default to `'hide'`. Per node, `data-capture="exclude"` is checked first, then `exclude`, then `filter`. The first omission decides the mode and stops evaluation for that node: if it matches `exclude`, `excludeMode` wins even when `filter` would reject it with a different mode. `filter` uses the v2 truthiness rule: any falsy return filters the node out.
|
|
309
273
|
|
|
310
|
-
###
|
|
274
|
+
### Callbacks that read changing application state
|
|
311
275
|
|
|
312
|
-
|
|
313
|
-
| ------------------------ | --------------- | -------------- | ----------- | ------------- |
|
|
314
|
-
| Small (200ร100) | **1.6 ms** | 3.3 ms | 68.0 ms | 14.3 ms |
|
|
315
|
-
| Modal (400ร300) | **2.9 ms** | 6.8 ms | 87.5 ms | 34.8 ms |
|
|
316
|
-
| Page View (1200ร800) | **17.5 ms** | 50.2 ms | 178.0 ms | 429.0 ms |
|
|
317
|
-
| Large Scroll (2000ร1500) | **54.0 ms** | 201.8 ms | 735.2 ms | 984.2 ms |
|
|
318
|
-
| Very Large (4000ร2000) | **171.4 ms** | 453.7 ms | 1,800.4 ms | 2,611.9 ms |
|
|
276
|
+
Captures with function-valued `filter`, `exclude`, `excludeStyleProps` or `fallbackURL` run fresh so applicable callbacks can read current application state on each new capture. They do not reuse an unchanged capture or an earlier callback's style/fallback decision. You do not need `invalidate` just because a callback's closure changed. That freshness has a cost: a function-valued `filter`, `exclude`, `excludeStyleProps` or `fallbackURL` turns off memoization and differential recapture for the capture, so a polling loop with a predicate pays a full capture on every tick. When the rule can be written as a selector, pass the selector and keep the reuse:
|
|
319
277
|
|
|
278
|
+
```js
|
|
279
|
+
let privateMode = false;
|
|
280
|
+
const options = {
|
|
281
|
+
exclude: node => privateMode && node.matches('[data-private]'),
|
|
282
|
+
excludeMode: 'remove'
|
|
283
|
+
};
|
|
284
|
+
const before = await snapdom(card, options);
|
|
285
|
+
privateMode = true;
|
|
286
|
+
const after = await snapdom(card, options); // evaluates the current policy
|
|
287
|
+
```
|
|
320
288
|
|
|
321
|
-
|
|
289
|
+
`before` still contains its original captured state; exporting it again does not apply a new policy. Use a new capture such as `after`. Keep exclusion and style predicates synchronous and boolean-returning. `invalidate: true` remains necessary after unobservable changes such as direct CSSOM edits.
|
|
322
290
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
291
|
+
Capture-affecting plugins suspend memoization unless they declare `pure: true`. Declare it only for deterministic hooks; timestamps and callbacks reading external state must run again. See the [v3 plugin contract](PLUGIN_SPEC.md).
|
|
292
|
+
|
|
293
|
+
## Limitations
|
|
294
|
+
|
|
295
|
+
- SnapDOM needs a browser DOM. A server-side Node.js process needs a browser environment to run it.
|
|
296
|
+
- Cross-origin images, fonts and stylesheets need readable resources or an appropriate proxy. `crossorigin` does not grant access unless the server also allows it. Cross-origin iframes use placeholders.
|
|
297
|
+
- SVG output includes HTML inside `<foreignObject>`. It is suitable for browsers; support varies in other SVG viewers and document tools.
|
|
298
|
+
- Output depends on browser rendering and canvas limits. Safari may fall back to PNG when WebP encoding is unavailable.
|
|
299
|
+
- Canvas, video and other changing surfaces are captured fresh. JavaScript CSSOM edits are not observable automatically; use `invalidate: true` after them.
|
|
300
|
+
- Core captures visible input values. Semantic plugins redact sensitive field values in their text/map output, but their attached image needs `redactInputs` or `exclude` if you want those pixels hidden too.
|
|
301
|
+
|
|
302
|
+
[Technical features and browser behavior](FEATURES.md) covers the details.
|
|
303
|
+
|
|
304
|
+
## Performance benchmarks
|
|
305
|
+
|
|
306
|
+
[Recorded measurements](BENCHMARKS.md) separate first captures, repeat captures and image-heavy scenes. The [live comparison](https://snapdom.dev/compare/live/) runs in your browser and identifies the package version it loads.
|
|
329
307
|
|
|
308
|
+
For a useful comparison, use the same scene, output format, scale and DPR. Compare the images as well as the times.
|
|
309
|
+
|
|
310
|
+
## Documentation
|
|
311
|
+
|
|
312
|
+
- [API](https://snapdom.dev/docs/api/) and [options](https://snapdom.dev/docs/options/)
|
|
313
|
+
- [Framework guides](https://snapdom.dev/guides/) and [how-to examples](https://snapdom.dev/how-to/)
|
|
314
|
+
- [Official plugins](packages/plugins/README.md), [plugin specification](PLUGIN_SPEC.md) and [contributing plugins](CONTRIBUTING_PLUGINS.md)
|
|
315
|
+
- [Architecture](ARCHITECTURE.md) and [technical features](FEATURES.md)
|
|
330
316
|
|
|
331
317
|
## Development
|
|
332
318
|
|
|
333
|
-
|
|
334
|
-
- `src/api/` โ Public API (`snapdom`, `preCache`)
|
|
335
|
-
- `src/core/` โ Capture pipeline, clone, prepare, plugins
|
|
336
|
-
- `src/modules/` โ Images, fonts, pseudo-elements, backgrounds, SVG
|
|
337
|
-
- `src/exporters/` โ toPng, toSvg, toBlob, etc.
|
|
338
|
-
- `dist/` โ Build output (`snapdom.js`, `snapdom.mjs`, `preCache.mjs`, `plugins.mjs`)
|
|
319
|
+
From this checkout:
|
|
339
320
|
|
|
340
|
-
**Build:**
|
|
341
321
|
```sh
|
|
342
|
-
git clone https://github.com/zumerlab/snapdom.git
|
|
343
|
-
cd snapdom
|
|
344
|
-
git checkout dev
|
|
345
322
|
npm install
|
|
323
|
+
npx playwright install
|
|
346
324
|
npm run compile
|
|
325
|
+
npm run lint
|
|
326
|
+
npm run test:types
|
|
327
|
+
npm run test:bundle
|
|
328
|
+
BROWSER=all npx vitest run __tests__ --browser.headless
|
|
329
|
+
npm run test:pack
|
|
347
330
|
```
|
|
348
331
|
|
|
349
|
-
|
|
350
|
-
```sh
|
|
351
|
-
npx playwright install # Required for browser tests
|
|
352
|
-
npm test
|
|
353
|
-
npm run test:benchmark
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
For detailed guidelines, see [CONTRIBUTING](https://github.com/zumerlab/snapdom/blob/main/CONTRIBUTING.md).
|
|
357
|
-
|
|
332
|
+
`npm run site` serves the docs with the local build. `npm test` checks lint without changing files; use `npm run lint:fix` to apply fixes. See [ARCHITECTURE.md](ARCHITECTURE.md) for implementation notes.
|
|
358
333
|
|
|
359
334
|
## Contributors
|
|
360
335
|
|
|
@@ -399,7 +374,7 @@ If you'd like to support this project too, you can [become a sponsor](https://gi
|
|
|
399
374
|
|
|
400
375
|
## Show your support
|
|
401
376
|
|
|
402
|
-
If SnapDOM saved you time, a
|
|
377
|
+
If SnapDOM saved you time, a star on GitHub helps other developers find it.
|
|
403
378
|
|
|
404
379
|
Shipping something built with SnapDOM? Add the badge to your README:
|
|
405
380
|
|
|
@@ -411,22 +386,22 @@ Shipping something built with SnapDOM? Add the badge to your README:
|
|
|
411
386
|
|
|
412
387
|
### Projects using SnapDOM
|
|
413
388
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
- [LobeHub](https://github.com/lobehub/lobehub)
|
|
417
|
-
- [
|
|
418
|
-
- [Sealos](https://github.com/labring/sealos)
|
|
419
|
-
- [Tencent tmagic-editor](https://github.com/Tencent/tmagic-editor)
|
|
420
|
-
- [Playroom](https://github.com/seek-oss/playroom)
|
|
421
|
-
- [GPT-Vis](https://github.com/antvis/GPT-Vis)
|
|
422
|
-
- [Rabby Wallet](https://github.com/RabbyHub/Rabby)
|
|
423
|
-
- [uMap](https://github.com/umap-project/umap)
|
|
424
|
-
- [ListenBrainz](https://github.com/metabrainz/listenbrainz-server)
|
|
425
|
-
- [Mind Elixir](https://github.com/SSShooter/mind-elixir-core)
|
|
426
|
-
- [Kong UI Components](https://github.com/Kong/public-ui-components)
|
|
427
|
-
- [SnapDIFF](https://zumerlab.com/snapdiff/)
|
|
428
|
-
|
|
429
|
-
See the full gallery at **[snapdom.dev/made-with](https://snapdom.dev/made-with/)**. Shipping SnapDOM? [Open a PR](https://github.com/zumerlab/snapdom/pulls) to add your project
|
|
389
|
+
Projects using SnapDOM include:
|
|
390
|
+
|
|
391
|
+
- [LobeHub](https://github.com/lobehub/lobehub) โ platform for operating AI agents
|
|
392
|
+
- [Hugging Face Chat UI](https://github.com/huggingface/chat-ui) โ HuggingChat interface with artifact preview captures
|
|
393
|
+
- [Sealos](https://github.com/labring/sealos) โ AI-native cloud operating system
|
|
394
|
+
- [Tencent tmagic-editor](https://github.com/Tencent/tmagic-editor) โ low-code page editor
|
|
395
|
+
- [Playroom](https://github.com/seek-oss/playroom) โ JSX design tool by SEEK
|
|
396
|
+
- [GPT-Vis](https://github.com/antvis/GPT-Vis) โ AI-friendly data viz by Ant Group's AntV
|
|
397
|
+
- [Rabby Wallet](https://github.com/RabbyHub/Rabby) โ browser wallet for EVM chains
|
|
398
|
+
- [uMap](https://github.com/umap-project/umap) โ OpenStreetMap map builder
|
|
399
|
+
- [ListenBrainz](https://github.com/metabrainz/listenbrainz-server) โ music tracker by MetaBrainz
|
|
400
|
+
- [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) โ mind-map core; recommends SnapDOM for image export
|
|
401
|
+
- [Kong UI Components](https://github.com/Kong/public-ui-components) โ Kong's dashboard renderer exports PDFs with SnapDOM
|
|
402
|
+
- [SnapDIFF](https://zumerlab.com/snapdiff/) โ in-browser visual regression testing *(by Zumerlab)*
|
|
403
|
+
|
|
404
|
+
See the full gallery at **[snapdom.dev/made-with](https://snapdom.dev/made-with/)**. Shipping SnapDOM? [Open a PR](https://github.com/zumerlab/snapdom/pulls) to add your project โ real, verifiable projects only.
|
|
430
405
|
|
|
431
406
|
## License
|
|
432
407
|
|