@zumer/snapdom 2.24.16 โ 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 +219 -257
- 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,334 +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
|
-
- [Acknowledgments](#acknowledgments)
|
|
93
|
-
- [License](#license)
|
|
80
|
+
The result keeps that capture even if the source element later changes. Call `snapdom(card)` again to capture its new state.
|
|
94
81
|
|
|
95
82
|
## Installation
|
|
96
83
|
|
|
97
|
-
|
|
84
|
+
Install the core and, when you need them, the official plugins; use matching major versions:
|
|
98
85
|
|
|
99
|
-
```
|
|
100
|
-
npm i @zumer/snapdom
|
|
101
|
-
yarn add @zumer/snapdom
|
|
86
|
+
```sh
|
|
87
|
+
npm i @zumer/snapdom @zumer/snapdom-plugins
|
|
102
88
|
```
|
|
103
89
|
|
|
104
|
-
|
|
90
|
+
Or load it in a browser:
|
|
105
91
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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>
|
|
111
99
|
```
|
|
112
100
|
|
|
113
|
-
|
|
101
|
+
As an ES module from a CDN:
|
|
114
102
|
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
103
|
+
```js
|
|
104
|
+
import { snapdom } from 'https://esm.sh/@zumer/snapdom';
|
|
105
|
+
import { htmlExport } from 'https://esm.sh/@zumer/snapdom-plugins/html-export';
|
|
118
106
|
```
|
|
119
107
|
|
|
120
|
-
|
|
121
|
-
when a beta is available, install its explicit version or announced tag.
|
|
108
|
+
`https://unpkg.com/@zumer/snapdom/dist/snapdom.mjs` serves the same module. Pin package versions in production.
|
|
122
109
|
|
|
110
|
+
To run the docs site against a local build of this checkout instead:
|
|
123
111
|
|
|
124
|
-
|
|
112
|
+
```sh
|
|
113
|
+
npm install
|
|
114
|
+
npm run compile
|
|
115
|
+
npm run site
|
|
116
|
+
```
|
|
125
117
|
|
|
126
|
-
|
|
127
|
-
<!-- Minified build -->
|
|
128
|
-
<script src="https://unpkg.com/@zumer/snapdom/dist/snapdom.js"></script>
|
|
118
|
+
The local site runs the local build. The public site's demos load the published package.
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
<script type="module">
|
|
132
|
-
import { snapdom } from "https://unpkg.com/@zumer/snapdom/dist/snapdom.mjs";
|
|
133
|
-
</script>
|
|
134
|
-
```
|
|
120
|
+
### Build outputs
|
|
135
121
|
|
|
136
|
-
|
|
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 |
|
|
137
127
|
|
|
138
|
-
|
|
139
|
-
<!-- Minified build (dev) -->
|
|
140
|
-
<script src="https://unpkg.com/@zumer/snapdom@dev/dist/snapdom.js"></script>
|
|
128
|
+
There is no CommonJS build. `@zumer/snapdom/plugins` and the package root share the same runtime and plugin registry.
|
|
141
129
|
|
|
142
|
-
|
|
143
|
-
<script type="module">
|
|
144
|
-
import { snapdom } from "https://unpkg.com/@zumer/snapdom@dev/dist/snapdom.mjs";
|
|
145
|
-
</script>
|
|
146
|
-
```
|
|
130
|
+
## Usage
|
|
147
131
|
|
|
148
|
-
|
|
132
|
+
### Choose an output
|
|
149
133
|
|
|
150
|
-
|
|
|
151
|
-
|
|
152
|
-
|
|
|
153
|
-
|
|
|
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 |
|
|
154
143
|
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
import { snapdom } from '@zumer/snapdom'; // โ dist/snapdom.mjs
|
|
158
|
-
```
|
|
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.
|
|
159
145
|
|
|
160
|
-
|
|
161
|
-
```html
|
|
162
|
-
<script src="https://unpkg.com/@zumer/snapdom/dist/snapdom.js"></script>
|
|
163
|
-
<script> snapdom.toPng(document.body).then(img => document.body.appendChild(img)); </script>
|
|
164
|
-
```
|
|
146
|
+
### Set size and content
|
|
165
147
|
|
|
166
|
-
**Subpath imports** (lighter bundle if you only need one):
|
|
167
148
|
```js
|
|
168
|
-
|
|
149
|
+
const result = await snapdom(card, {
|
|
150
|
+
width: 800,
|
|
151
|
+
dpr: 1,
|
|
152
|
+
backgroundColor: '#ffffff',
|
|
153
|
+
exclude: '.capture-ignore',
|
|
154
|
+
excludeMode: 'remove'
|
|
155
|
+
});
|
|
169
156
|
```
|
|
170
157
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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.
|
|
159
|
+
|
|
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 |
|
|
174
|
+
|
|
175
|
+
[All options](https://snapdom.dev/docs/options/) include shadows, transforms, fonts, CORS, fallbacks and layout reconciliation.
|
|
176
|
+
|
|
177
|
+
### Export HTML or structured context
|
|
178
|
+
|
|
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.
|
|
180
|
+
|
|
175
181
|
```js
|
|
176
|
-
import {
|
|
182
|
+
import { htmlExport, contextExport } from '@zumer/snapdom-plugins';
|
|
183
|
+
|
|
184
|
+
const result = await snapdom(card, {
|
|
185
|
+
plugins: [htmlExport(), contextExport({ format: 'json' })]
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const html = await result.toHtml();
|
|
189
|
+
const context = await result.toContext();
|
|
177
190
|
```
|
|
178
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).
|
|
179
193
|
|
|
180
|
-
|
|
194
|
+
### Capture HTML strings
|
|
181
195
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
```js
|
|
197
|
+
const result = await snapdom.fromString('<article>Hello</article>');
|
|
198
|
+
const image = await result.toPng();
|
|
199
|
+
```
|
|
186
200
|
|
|
187
|
-
|
|
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.
|
|
188
202
|
|
|
189
|
-
|
|
203
|
+
### Feed a WebGL texture
|
|
190
204
|
|
|
191
205
|
```js
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
await
|
|
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
|
+
}
|
|
198
214
|
```
|
|
199
215
|
|
|
200
|
-
|
|
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.
|
|
201
217
|
|
|
202
|
-
|
|
218
|
+
## What's new in v3
|
|
219
|
+
|
|
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.
|
|
203
225
|
|
|
204
226
|
```js
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
document.body.appendChild(png);
|
|
227
|
+
snapdom.preCapture();
|
|
228
|
+
button.onclick = () => snapdom.toPng(card);
|
|
208
229
|
```
|
|
209
230
|
|
|
210
|
-
|
|
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/).
|
|
211
232
|
|
|
212
|
-
|
|
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'`.
|
|
213
234
|
|
|
214
|
-
|
|
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.
|
|
215
236
|
|
|
216
|
-
|
|
217
|
-
- SnapDOM relies on Canvas, which enforces strict CORS policies. The browser's rendering engine is more permissive for onโscreen display.
|
|
237
|
+
## Migrating from v2
|
|
218
238
|
|
|
219
|
-
|
|
239
|
+
The main capture pattern remains `snapdom(element, options)`. This guide compares v3 with **v2.24.16**; review these changes before upgrading:
|
|
220
240
|
|
|
221
|
-
|
|
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']>` |
|
|
222
255
|
|
|
223
|
-
|
|
224
|
-
<link
|
|
225
|
-
rel="stylesheet"
|
|
226
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
|
|
227
|
-
crossorigin="anonymous"
|
|
228
|
-
/>
|
|
229
|
-
```
|
|
256
|
+
### Use filter and exclude together
|
|
230
257
|
|
|
231
|
-
|
|
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:
|
|
232
259
|
|
|
233
|
-
|
|
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
|
+
```
|
|
234
269
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
- **[API reference](https://snapdom.dev/docs/api/)**: the `snapdom()` reusable object, shortcut methods, and exporter-specific options.
|
|
238
|
-
- **[Options](https://snapdom.dev/docs/options/)**: every capture option (`scale`, `dpr`, `embedFonts`, `useProxy`, `exclude`/`filter`, `compress`, `outerTransforms`, `outerShadows`, `cache`โฆ) explained with examples.
|
|
239
|
-
- **[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).
|
|
240
|
-
- **[Cache & preCache](https://snapdom.dev/docs/cache/)**: control caching between captures and preload resources.
|
|
241
|
-
|
|
242
|
-
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/)**
|
|
243
|
-
|
|
244
|
-
### API at a glance
|
|
245
|
-
|
|
246
|
-
`snapdom(el, options?)` returns a reusable object (`toPng`, `toSvg`, `toCanvas`, `toBlob`, `toJpg`, `toWebp`, `download`, `url`). For single exports, use the shortcuts:
|
|
247
|
-
|
|
248
|
-
| Method | Description |
|
|
249
|
-
| ------------------------------ | --------------------------------- |
|
|
250
|
-
| `snapdom.toSvg(el, options?)` | Returns an SVG `HTMLImageElement` |
|
|
251
|
-
| `snapdom.toCanvas(el, options?)` | Returns a `Canvas` |
|
|
252
|
-
| `snapdom.toBlob(el, options?)` | Returns an SVG or raster `Blob` |
|
|
253
|
-
| `snapdom.toPng(el, options?)` | Returns a PNG image |
|
|
254
|
-
| `snapdom.toJpg(el, options?)` | Returns a JPG image |
|
|
255
|
-
| `snapdom.toWebp(el, options?)` | Returns a WebP image |
|
|
256
|
-
| `snapdom.download(el, options?)` | Triggers a download |
|
|
257
|
-
|
|
258
|
-
### Options at a glance
|
|
259
|
-
|
|
260
|
-
All options are optional and can be passed to `snapdom(el, options)` or any shortcut method.
|
|
261
|
-
|
|
262
|
-
| Option | Type | Default | Description |
|
|
263
|
-
| ------ | ---- | ------- | ----------- |
|
|
264
|
-
| `scale` | `number` | `1` | Output scale multiplier |
|
|
265
|
-
| `dpr` | `number` | `devicePixelRatio` | Pixel density of the rasterized output |
|
|
266
|
-
| `width` / `height` | `number` | `null` | Target output size (keeps aspect ratio if only one is set) |
|
|
267
|
-
| `backgroundColor` | `string` | `null` (`#ffffff` for JPEG/WebP) | Background fill |
|
|
268
|
-
| `quality` | `number` | `0.92` | JPEG/WebP quality (0โ1) |
|
|
269
|
-
| `format` | `'png' \| 'jpeg' \| 'webp' \| 'svg'` | `'png'` | Format for `download()` |
|
|
270
|
-
| `type` | `string` | `'svg'` | Blob type for `toBlob()` (`'png'`, `'jpeg'`โฆ) |
|
|
271
|
-
| `filename` | `string` | `'snapDOM'` | Download filename |
|
|
272
|
-
| `embedFonts` | `boolean` | `false` | Inline `@font-face` so text renders with your real fonts |
|
|
273
|
-
| `iconFonts` | `string \| RegExp \| array` | `[]` | Icon font families (always embedded) |
|
|
274
|
-
| `localFonts` | `array` | `[]` | Explicit fonts: `{ family, src, weight?, style? }` |
|
|
275
|
-
| `excludeFonts` | `object` | none | Skip fonts by family / domain / subset |
|
|
276
|
-
| `exclude` | `string[]` | `[]` | CSS selectors to leave out of the capture |
|
|
277
|
-
| `filter` | `(el) => boolean` | `null` | Keep-predicate (return `false` to drop a node) |
|
|
278
|
-
| `excludeMode` / `filterMode` | `'hide' \| 'remove'` | `'hide'` | How excluded nodes are handled |
|
|
279
|
-
| `clip` | `'viewport' \| {x, y, width, height}` | `null` | Capture only a region; offscreen content is pruned |
|
|
280
|
-
| `compress` | `boolean` | `true` | Downsample inlined images to their visible resolution |
|
|
281
|
-
| `useProxy` | `string` | `''` | CORS proxy prefix for cross-origin images |
|
|
282
|
-
| `fallbackURL` | `string \| fn` | none | Fallback image for broken `<img>` |
|
|
283
|
-
| `cache` | `'soft' \| 'auto' \| 'full' \| 'disabled'` | `'soft'` | Cache policy between captures |
|
|
284
|
-
| `outerTransforms` | `boolean` | `true` | Keep root translate/rotate in the output |
|
|
285
|
-
| `outerShadows` | `boolean` | `false` | Expand bounds to include root shadows/blur/outline |
|
|
286
|
-
| `fast` | `boolean` | `true` | Skip idle delays for faster capture |
|
|
287
|
-
| `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 |
|
|
288
|
-
| `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 |
|
|
289
|
-
| `invalidate` | `boolean` | `false` | With `burst: true`, forces a fresh capture for changes automatic tracking can't see (canvas draws, programmatic CSSOM edits) |
|
|
290
|
-
| `plugins` | `array` | none | Per-capture plugins (override globals by name) |
|
|
291
|
-
|
|
292
|
-
๐ **[Full API & every option, explained with examples โ snapdom.dev/docs](https://snapdom.dev/docs/)**
|
|
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.
|
|
293
271
|
|
|
294
|
-
|
|
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.
|
|
295
273
|
|
|
296
|
-
|
|
297
|
-
* 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.
|
|
298
|
-
* `@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)
|
|
299
|
-
* **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.
|
|
300
|
-
* **Custom scrollbar styles** (`::-webkit-scrollbar`): Applied only when the element has *not* been scrolled. When scrolled, the viewport content is captured without the scrollbar.
|
|
274
|
+
### Callbacks that read changing application state
|
|
301
275
|
|
|
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:
|
|
302
277
|
|
|
303
|
-
|
|
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
|
+
```
|
|
304
288
|
|
|
305
|
-
|
|
306
|
-
Values are **average capture time (ms)** โ lower is better.
|
|
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.
|
|
307
290
|
|
|
308
|
-
|
|
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).
|
|
309
292
|
|
|
310
|
-
|
|
311
|
-
| ------------------------ | --------------- | -------------- | ----------- | ------------- |
|
|
312
|
-
| Small (200ร100) | **0.5 ms** | 0.8 ms | 67.7 ms | 3.1 ms |
|
|
313
|
-
| Modal (400ร300) | **0.5 ms** | 0.8 ms | 75.5 ms | 3.6 ms |
|
|
314
|
-
| Page View (1200ร800) | **0.5 ms** | 0.8 ms | 114.2 ms | 3.3 ms |
|
|
315
|
-
| Large Scroll (2000ร1500) | **0.5 ms** | 0.8 ms | 186.3 ms | 3.2 ms |
|
|
316
|
-
| Very Large (4000ร2000) | **0.5 ms** | 0.9 ms | 425.9 ms | 3.3 ms |
|
|
293
|
+
## Limitations
|
|
317
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.
|
|
318
301
|
|
|
319
|
-
|
|
302
|
+
[Technical features and browser behavior](FEATURES.md) covers the details.
|
|
320
303
|
|
|
321
|
-
|
|
322
|
-
| ------------------------ | --------------- | -------------- | ----------- | ------------- |
|
|
323
|
-
| Small (200ร100) | **1.6 ms** | 3.3 ms | 68.0 ms | 14.3 ms |
|
|
324
|
-
| Modal (400ร300) | **2.9 ms** | 6.8 ms | 87.5 ms | 34.8 ms |
|
|
325
|
-
| Page View (1200ร800) | **17.5 ms** | 50.2 ms | 178.0 ms | 429.0 ms |
|
|
326
|
-
| Large Scroll (2000ร1500) | **54.0 ms** | 201.8 ms | 735.2 ms | 984.2 ms |
|
|
327
|
-
| Very Large (4000ร2000) | **171.4 ms** | 453.7 ms | 1,800.4 ms | 2,611.9 ms |
|
|
304
|
+
## Performance benchmarks
|
|
328
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
|
|
|
330
|
-
|
|
308
|
+
For a useful comparison, use the same scene, output format, scale and DPR. Compare the images as well as the times.
|
|
331
309
|
|
|
332
|
-
|
|
333
|
-
git clone https://github.com/zumerlab/snapdom.git
|
|
334
|
-
cd snapdom
|
|
335
|
-
npm install
|
|
336
|
-
npm run test:benchmark
|
|
337
|
-
```
|
|
310
|
+
## Documentation
|
|
338
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)
|
|
339
316
|
|
|
340
317
|
## Development
|
|
341
318
|
|
|
342
|
-
|
|
343
|
-
- `src/api/` โ Public API (`snapdom`, `preCache`)
|
|
344
|
-
- `src/core/` โ Capture pipeline, clone, prepare, plugins
|
|
345
|
-
- `src/modules/` โ Images, fonts, pseudo-elements, backgrounds, SVG
|
|
346
|
-
- `src/exporters/` โ toPng, toSvg, toBlob, etc.
|
|
347
|
-
- `dist/` โ Build output (`snapdom.js`, `snapdom.mjs`, `preCache.mjs`, `plugins.mjs`)
|
|
319
|
+
From this checkout:
|
|
348
320
|
|
|
349
|
-
**Build:**
|
|
350
321
|
```sh
|
|
351
|
-
git clone https://github.com/zumerlab/snapdom.git
|
|
352
|
-
cd snapdom
|
|
353
|
-
git checkout dev
|
|
354
322
|
npm install
|
|
323
|
+
npx playwright install
|
|
355
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
|
|
356
330
|
```
|
|
357
331
|
|
|
358
|
-
|
|
359
|
-
```sh
|
|
360
|
-
npx playwright install # Required for browser tests
|
|
361
|
-
npm test
|
|
362
|
-
npm run test:benchmark
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
For detailed guidelines, see [CONTRIBUTING](https://github.com/zumerlab/snapdom/blob/main/CONTRIBUTING.md).
|
|
366
|
-
|
|
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.
|
|
367
333
|
|
|
368
334
|
## Contributors
|
|
369
335
|
|
|
@@ -408,7 +374,7 @@ If you'd like to support this project too, you can [become a sponsor](https://gi
|
|
|
408
374
|
|
|
409
375
|
## Show your support
|
|
410
376
|
|
|
411
|
-
If SnapDOM saved you time, a
|
|
377
|
+
If SnapDOM saved you time, a star on GitHub helps other developers find it.
|
|
412
378
|
|
|
413
379
|
Shipping something built with SnapDOM? Add the badge to your README:
|
|
414
380
|
|
|
@@ -420,26 +386,22 @@ Shipping something built with SnapDOM? Add the badge to your README:
|
|
|
420
386
|
|
|
421
387
|
### Projects using SnapDOM
|
|
422
388
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
- [LobeHub](https://github.com/lobehub/lobehub)
|
|
426
|
-
- [
|
|
427
|
-
- [Sealos](https://github.com/labring/sealos)
|
|
428
|
-
- [Tencent tmagic-editor](https://github.com/Tencent/tmagic-editor)
|
|
429
|
-
- [Playroom](https://github.com/seek-oss/playroom)
|
|
430
|
-
- [GPT-Vis](https://github.com/antvis/GPT-Vis)
|
|
431
|
-
- [Rabby Wallet](https://github.com/RabbyHub/Rabby)
|
|
432
|
-
- [uMap](https://github.com/umap-project/umap)
|
|
433
|
-
- [ListenBrainz](https://github.com/metabrainz/listenbrainz-server)
|
|
434
|
-
- [Mind Elixir](https://github.com/SSShooter/mind-elixir-core)
|
|
435
|
-
- [Kong UI Components](https://github.com/Kong/public-ui-components)
|
|
436
|
-
- [SnapDIFF](https://zumerlab.com/snapdiff/)
|
|
437
|
-
|
|
438
|
-
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
|
|
439
|
-
|
|
440
|
-
## Acknowledgments
|
|
441
|
-
|
|
442
|
-
Thanks to [Anthropic](https://claude.com/contact-sales/claude-for-oss) and [OpenAI](https://openai.com/form/codex-for-oss/) for supporting my open-source work by providing premium access to Claude and ChatGPT.
|
|
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.
|
|
443
405
|
|
|
444
406
|
## License
|
|
445
407
|
|