@zumer/snapdom 2.15.0-dev.1 → 2.15.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 +25 -608
- package/dist/plugins.mjs +1 -1
- package/dist/preCache.mjs +1 -1
- package/dist/snapdom.js +7 -7
- package/dist/snapdom.mjs +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="
|
|
2
|
+
<a href="https://snapdom.dev">
|
|
3
3
|
<img src="https://raw.githubusercontent.com/zumerlab/snapdom/main/docs/assets/newhero.png" width="80%">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://
|
|
8
|
+
<a href="https://snapdom.dev">
|
|
9
|
+
<img alt="Website" src="https://img.shields.io/badge/Website-snapdom.dev-2ea44f?style=flat-square">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@zumer/snapdom">
|
|
9
12
|
<img alt="NPM version" src="https://img.shields.io/npm/v/@zumer/snapdom?style=flat-square&label=Version">
|
|
10
13
|
</a>
|
|
11
14
|
<a href="https://www.npmjs.com/package/@zumer/snapdom">
|
|
@@ -36,6 +39,8 @@
|
|
|
36
39
|
**SnapDOM** is a next-generation **DOM Capture Engine** — the fast, modern alternative to **html2canvas**, **dom-to-image**, and **html-to-image**.
|
|
37
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.
|
|
38
41
|
|
|
42
|
+
> 📖 **[Documentation, guides & live demos → snapdom.dev](https://snapdom.dev)**
|
|
43
|
+
|
|
39
44
|
* Full DOM capture
|
|
40
45
|
* Embedded styles, pseudo-elements, and fonts
|
|
41
46
|
* Export to SVG, PNG, JPG, WebP, `canvas`, or Blob
|
|
@@ -45,7 +50,7 @@ It converts any DOM subtree into a self-contained representation that can be exp
|
|
|
45
50
|
* Support CSS counter() and CSS counters()
|
|
46
51
|
* Support `...` line-clamp
|
|
47
52
|
|
|
48
|
-
##
|
|
53
|
+
## Website & Live Demos
|
|
49
54
|
|
|
50
55
|
[https://snapdom.dev](https://snapdom.dev)
|
|
51
56
|
|
|
@@ -111,56 +116,17 @@ Plugin hooks: `beforeSnap` → `beforeClone` → `afterClone` → `beforeRender`
|
|
|
111
116
|
- [Quick Start](#quick-start)
|
|
112
117
|
- [Capture Flow](#capture-flow)
|
|
113
118
|
- [Installation](#installation)
|
|
114
|
-
|
|
115
|
-
- [NPM / Yarn (dev builds)](#npm--yarn-dev-builds)
|
|
116
|
-
- [CDN (stable)](#cdn-stable)
|
|
117
|
-
- [CDN (dev builds)](#cdn-dev-builds)
|
|
118
|
-
- [Build Outputs](#build-outputs--tree-shaking)
|
|
119
|
+
- [Build Outputs](#build-outputs)
|
|
119
120
|
- [Usage](#usage)
|
|
120
|
-
|
|
121
|
-
- [One-step shortcuts](#one-step-shortcuts)
|
|
122
|
-
- [API](#api)
|
|
123
|
-
- [snapdom(el, options?)](#snapdomel-options)
|
|
124
|
-
- [Shortcut methods](#shortcut-methods)
|
|
125
|
-
- [Options](#options)
|
|
126
|
-
- [debug](#debug)
|
|
127
|
-
- [Fallback image on `<img>` load failure](#fallback-image-on-img-load-failure)
|
|
128
|
-
- [Dimensions (`scale`, `width`, `height`)](#dimensions-scale-width-height)
|
|
129
|
-
- [Cross-Origin Images & Fonts (`useProxy`)](#cross-origin-images--fonts-useproxy)
|
|
130
|
-
- [Fonts](#fonts)
|
|
131
|
-
- [embedFonts](#embedfonts)
|
|
132
|
-
- [localFonts](#localfonts)
|
|
133
|
-
- [iconFonts](#iconfonts)
|
|
134
|
-
- [excludeFonts](#excludefonts)
|
|
135
|
-
- [Filtering nodes: `exclude` vs `filter`](#filtering-nodes-exclude-vs-filter)
|
|
136
|
-
- [outerTransforms](#outerTransforms)
|
|
137
|
-
- [outerShadows](#no-shadows)
|
|
138
|
-
- [Cache control](#cache-control)
|
|
139
|
-
- [preCache](#precache--optional-helper)
|
|
140
|
-
- [Plugins (BETA)](#plugins-beta)
|
|
141
|
-
- [Official Plugins](#official-plugins)
|
|
142
|
-
- [Community Plugins](#community-plugins)
|
|
143
|
-
- [Build a Plugin in 5 Minutes](#build-a-plugin-in-5-minutes)
|
|
144
|
-
- [Registering Plugins](#registering-plugins)
|
|
145
|
-
- [Plugin Lifecycle Hooks](#plugin-lifecycle-hooks)
|
|
146
|
-
- [Context Object](#context-object)
|
|
147
|
-
- [Custom Exports via Plugins](#custom-exports-via-plugins)
|
|
148
|
-
- [Example: Overlay Filter Plugin](#example-overlay-filter-plugin)
|
|
149
|
-
- [Full Plugin Template](#full-plugin-template)
|
|
121
|
+
- [Documentation](#documentation) — full API, Options, Plugins & Cache reference on [snapdom.dev/docs](https://snapdom.dev/docs/)
|
|
150
122
|
- [Limitations](#limitations)
|
|
151
|
-
- [
|
|
152
|
-
- [Simple elements](#simple-elements)
|
|
153
|
-
- [Complex elements](#complex-elements)
|
|
154
|
-
- [Run the benchmarks](#run-the-benchmarks)
|
|
123
|
+
- [Performance Benchmarks](#performance-benchmarks)
|
|
155
124
|
- [Roadmap](#roadmap)
|
|
156
125
|
- [Development](#development)
|
|
157
|
-
- [Contributors
|
|
158
|
-
- [
|
|
159
|
-
- [Star History](#star-history)
|
|
126
|
+
- [Contributors](#contributors)
|
|
127
|
+
- [Sponsors](#sponsors)
|
|
160
128
|
- [License](#license)
|
|
161
129
|
|
|
162
|
-
|
|
163
|
-
|
|
164
130
|
## Installation
|
|
165
131
|
|
|
166
132
|
### NPM / Yarn (stable)
|
|
@@ -261,579 +227,30 @@ const blob = await snapdom.toBlob(el);
|
|
|
261
227
|
document.body.appendChild(png);
|
|
262
228
|
```
|
|
263
229
|
|
|
264
|
-
##
|
|
230
|
+
## Documentation
|
|
265
231
|
|
|
266
|
-
|
|
232
|
+
The full reference lives on **[snapdom.dev/docs](https://snapdom.dev/docs/)** — kept there so it stays in sync and searchable:
|
|
267
233
|
|
|
268
|
-
|
|
234
|
+
- **[API reference](https://snapdom.dev/docs/api/)** — the `snapdom()` reusable object, shortcut methods, and exporter-specific options.
|
|
235
|
+
- **[Options](https://snapdom.dev/docs/options/)** — every capture option (`scale`, `dpr`, `embedFonts`, `useProxy`, `exclude`/`filter`, `compress`, `outerTransforms`, `outerShadows`, `cache`…) explained with examples.
|
|
236
|
+
- **[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).
|
|
237
|
+
- **[Cache & preCache](https://snapdom.dev/docs/cache/)** — control caching between captures and preload resources.
|
|
269
238
|
|
|
270
|
-
|
|
271
|
-
{
|
|
272
|
-
url: string;
|
|
273
|
-
toRaw(): string;
|
|
274
|
-
toImg(): Promise<HTMLImageElement>; // deprecated
|
|
275
|
-
toSvg(): Promise<HTMLImageElement>;
|
|
276
|
-
toCanvas(): Promise<HTMLCanvasElement>;
|
|
277
|
-
toBlob(options?): Promise<Blob>;
|
|
278
|
-
toPng(options?): Promise<HTMLImageElement>;
|
|
279
|
-
toJpg(options?): Promise<HTMLImageElement>;
|
|
280
|
-
toWebp(options?): Promise<HTMLImageElement>;
|
|
281
|
-
download(options?): Promise<void>;
|
|
282
|
-
}
|
|
283
|
-
```
|
|
239
|
+
### API at a glance
|
|
284
240
|
|
|
285
|
-
|
|
241
|
+
`snapdom(el, options?)` returns a reusable object (`toPng`, `toSvg`, `toCanvas`, `toBlob`, `toJpg`, `toWebp`, `download`, `url`). For single exports, use the shortcuts:
|
|
286
242
|
|
|
287
|
-
| Method
|
|
243
|
+
| Method | Description |
|
|
288
244
|
| ------------------------------ | --------------------------------- |
|
|
289
|
-
| `snapdom.toImg(el, options?)` | Returns an SVG `HTMLImageElement` (deprecated) |
|
|
290
245
|
| `snapdom.toSvg(el, options?)` | Returns an SVG `HTMLImageElement` |
|
|
291
|
-
| `snapdom.toCanvas(el, options?)` | Returns a `Canvas`
|
|
246
|
+
| `snapdom.toCanvas(el, options?)` | Returns a `Canvas` |
|
|
292
247
|
| `snapdom.toBlob(el, options?)` | Returns an SVG or raster `Blob` |
|
|
293
248
|
| `snapdom.toPng(el, options?)` | Returns a PNG image |
|
|
294
249
|
| `snapdom.toJpg(el, options?)` | Returns a JPG image |
|
|
295
250
|
| `snapdom.toWebp(el, options?)` | Returns a WebP image |
|
|
296
|
-
| `snapdom.download(el, options?)` | Triggers a download
|
|
297
|
-
|
|
298
|
-
### Exporter-specific options
|
|
299
|
-
|
|
300
|
-
Some exporters accept a small set of **export-only options** in addition to the global capture options.
|
|
301
|
-
|
|
302
|
-
#### `download()`
|
|
303
|
-
|
|
304
|
-
| Option | Type | Default | Description |
|
|
305
|
-
| --- | --- | --- | --- |
|
|
306
|
-
| `filename` | `string` | `snapdom` | Download name. |
|
|
307
|
-
| `format` | `"png" \| "jpeg" \| "jpg" \| "webp" \| "svg"` | `"png"` | Output format for the downloaded file. |
|
|
308
|
-
|
|
309
|
-
**Example:**
|
|
310
|
-
|
|
311
|
-
```js
|
|
312
|
-
await result.download({
|
|
313
|
-
format: 'jpg',
|
|
314
|
-
quality: 0.92,
|
|
315
|
-
filename: 'my-capture'
|
|
316
|
-
});
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
#### `toBlob()`
|
|
320
|
-
|
|
321
|
-
| Option | Type | Default | Description |
|
|
322
|
-
| --- | --- | --- | --- |
|
|
323
|
-
| `type` | `"svg" \| "png" \| "jpeg" \| "jpg" \| "webp"` | `"svg"` | Blob type to generate. |
|
|
324
|
-
|
|
325
|
-
**Example:**
|
|
326
|
-
|
|
327
|
-
```js
|
|
328
|
-
const blob = await result.toBlob({ type: 'jpeg', quality: 0.92 });
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
## Options
|
|
332
|
-
|
|
333
|
-
All capture methods accept an `options` object:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
| Option | Type | Default | Description |
|
|
337
|
-
| ----------------- | -------- | -------- | ----------------------------------------------- |
|
|
338
|
-
| `debug` | boolean | `false` | When `true`, logs suppressed errors to `console.warn` for troubleshooting |
|
|
339
|
-
| `fast` | boolean | `true` | Skips small idle delays for faster results |
|
|
340
|
-
| `embedFonts` | boolean | `false` | Inlines non-icon fonts (icon fonts always on) |
|
|
341
|
-
| `localFonts` | array | `[]` | Local fonts `{ family, src, weight?, style? }` |
|
|
342
|
-
| `iconFonts` | string\|RegExp\|Array | `[]` | Extra icon font matchers |
|
|
343
|
-
| `excludeFonts` | object | `{}` | Exclude families/domains/subsets during embedding |
|
|
344
|
-
| `scale` | number | `1` | Output scale multiplier |
|
|
345
|
-
| `dpr` | number | `devicePixelRatio` | Device pixel ratio |
|
|
346
|
-
| `width` | number | - | Output width |
|
|
347
|
-
| `height` | number | - | Output height |
|
|
348
|
-
| `backgroundColor` | string | `"#fff"` | Fallback color for JPG/WebP |
|
|
349
|
-
| `quality` | number | `1` | Quality for JPG/WebP (0 to 1) |
|
|
350
|
-
| `useProxy` | string | `''` | Proxy base for CORS fallbacks |
|
|
351
|
-
| `exclude` | string[] | - | CSS selectors to exclude |
|
|
352
|
-
| `excludeMode` | `"hide"`\|`"remove"` | `"hide"` | How `exclude` is applied |
|
|
353
|
-
| `filter` | function | - | Custom predicate `(el) => boolean` |
|
|
354
|
-
| `filterMode` | `"hide"`\|`"remove"` | `"hide"` | How `filter` is applied |
|
|
355
|
-
| `cache` | string | `"soft"` | `disabled` \| `soft` \| `auto` \| `full` |
|
|
356
|
-
| `placeholders` | boolean | `true` | Show placeholders for images/CORS iframes |
|
|
357
|
-
| `fallbackURL` | string \| function | - | Fallback image for `<img>` load failure |
|
|
358
|
-
| `outerTransforms` | boolean | `true` | When `false` removes `translate/rotate` but preserves `scale/skew`, producing a flat, reusable capture |
|
|
359
|
-
| `outerShadows` | boolean | `false` | Do not expand the root’s bounding box for shadows/blur/outline, and strip those visual effects from the cloned root |
|
|
360
|
-
| `safariWarmupAttempts` | number | `3` | Safari only: iterations to prime font/decode (WebKit #219770). Use `1` if 3 causes lag |
|
|
361
|
-
| `compress` | boolean | `true` | Downsample inlined raster images to their visible resolution. Faster raster captures + smaller SVG output, fidelity-neutral. Set `false` to embed images verbatim |
|
|
362
|
-
|
|
363
|
-
### debug
|
|
364
|
-
|
|
365
|
-
When `debug: true`, SnapDOM logs normally suppressed errors to `console.warn` (with the `[snapdom]` prefix). Useful for troubleshooting capture issues (canvas failures, blob resolution, style stripping, etc.) without noisy output in production.
|
|
366
|
-
|
|
367
|
-
```js
|
|
368
|
-
await snapdom.toPng(el, { debug: true });
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### compress
|
|
372
|
-
|
|
373
|
-
Inlined raster images are embedded at their full natural resolution even when shown in a small box — pixels the output can never display, which only bloat the payload and slow rasterization. With `compress` (on by default) SnapDOM downsamples each oversized raster to (a touch below) the resolution actually visible (`display box × scale × dpr`), preserving aspect ratio and never upscaling. The source codec is kept (PNGs stay lossless), and because the image was already shown far smaller than its natural size the quality loss is imperceptible in practice. Images already at or below their visible size are left untouched. Covers `<img>` (incl. cloned canvas/video), non-repeating CSS `background-image`, and SVG `<image>`.
|
|
374
|
-
|
|
375
|
-
The benefit depends on the export:
|
|
376
|
-
|
|
377
|
-
- **Raster** (`toPng` / `toJpg` / `toCanvas` / `toBlob`): **much faster** — the browser decodes/composites small images instead of huge ones. Output size is essentially unchanged (the final bitmap is the same visible pixels).
|
|
378
|
-
- **SVG** (`toSvg` / raw): **much smaller** output — the downsampled images are embedded in the SVG.
|
|
379
|
-
|
|
380
|
-
It is a no-op for images already shown at (or below) their natural resolution, so the common case pays virtually nothing. Pass `compress: false` to embed images verbatim (e.g. if you intend to scale the SVG up later).
|
|
381
|
-
|
|
382
|
-
```js
|
|
383
|
-
// default: images downsampled to visible resolution
|
|
384
|
-
await snapdom.toPng(el);
|
|
385
|
-
|
|
386
|
-
// keep images at full resolution
|
|
387
|
-
await snapdom.toPng(el, { compress: false });
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Fallback image on `<img>` load failure
|
|
391
|
-
|
|
392
|
-
Provide a default image for failed `<img>` loads. You can pass a fixed URL or a callback that receives measured dimensions and returns a URL (handy to generate dynamic placeholders).
|
|
393
|
-
|
|
394
|
-
```js
|
|
395
|
-
// 1) Fixed URL fallback
|
|
396
|
-
await snapdom.toSvg(element, {
|
|
397
|
-
fallbackURL: '/images/fallback.png'
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
// 2) Dynamic placeholder via callback
|
|
401
|
-
await snapdom.toSvg(element, {
|
|
402
|
-
fallbackURL: ({ width: 300, height: 150 }) =>
|
|
403
|
-
`https://placehold.co/${width}x${height}`
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// 3) With proxy (if your fallback host has no CORS)
|
|
407
|
-
await snapdom.toSvg(element, {
|
|
408
|
-
fallbackURL: ({ width = 300, height = 150 }) =>
|
|
409
|
-
`https://dummyimage.com/${width}x${height}/cccccc/666.png&text=img`,
|
|
410
|
-
useProxy: 'https://proxy.corsfix.com/?'
|
|
411
|
-
});
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
Notes:
|
|
415
|
-
- If the fallback image also fails to load, snapDOM replaces the `<img>` with a placeholder block preserving width/height.
|
|
416
|
-
- Width/height used by the callback are gathered from the original element (dataset, style/attrs, etc.) when available.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
### Dimensions (`scale`, `width`, `height`)
|
|
420
|
-
|
|
421
|
-
* If `scale` is provided, it **takes precedence** over `width`/`height`.
|
|
422
|
-
* If only `width` is provided, height scales proportionally (and vice versa).
|
|
423
|
-
* Providing both `width` and `height` forces an exact size (may distort).
|
|
424
|
-
|
|
425
|
-
### Cross-Origin Images & Fonts (`useProxy`)
|
|
426
|
-
|
|
427
|
-
By default snapDOM tries `crossOrigin="anonymous"` (or `use-credentials` for same-origin). If an asset is CORS-blocked, you can set `useProxy` to a prefix URL that forwards the actual `src`:
|
|
428
|
-
|
|
429
|
-
```js
|
|
430
|
-
await snapdom.toPng(el, {
|
|
431
|
-
useProxy: 'https://proxy.corsfix.com/?' // Note: Any cors proxy could be used 'https://proxy.corsfix.com/?'
|
|
432
|
-
});
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
* The proxy is only used as a **fallback**; same-origin and CORS-enabled assets skip it.
|
|
437
|
-
|
|
438
|
-
### Fonts
|
|
439
|
-
|
|
440
|
-
#### `embedFonts`
|
|
441
|
-
When `true`, snapDOM embeds **non-icon** `@font-face` rules detected as used within the captured subtree. Icon fonts (Font Awesome, Material Icons, etc.) are embedded **always**.
|
|
442
|
-
|
|
443
|
-
#### `localFonts`
|
|
444
|
-
If you serve fonts yourself or have data URLs, you can declare them here to avoid extra CSS discovery:
|
|
445
|
-
|
|
446
|
-
```js
|
|
447
|
-
await snapdom.toPng(el, {
|
|
448
|
-
embedFonts: true,
|
|
449
|
-
localFonts: [
|
|
450
|
-
{ family: 'Inter', src: '/fonts/Inter-Variable.woff2', weight: 400, style: 'normal' },
|
|
451
|
-
{ family: 'Inter', src: '/fonts/Inter-Italic.woff2', style: 'italic' }
|
|
452
|
-
]
|
|
453
|
-
});
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
#### `iconFonts`
|
|
457
|
-
Add custom icon families (names or regex matchers). Useful for private icon sets:
|
|
458
|
-
|
|
459
|
-
```js
|
|
460
|
-
await snapdom.toPng(el, {
|
|
461
|
-
iconFonts: ['MyIcons', /^(Remix|Feather) Icons?$/i]
|
|
462
|
-
});
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
#### `excludeFonts`
|
|
466
|
-
Skip specific non-icon fonts to speed up capture or avoid unnecessary downloads.
|
|
467
|
-
|
|
468
|
-
```js
|
|
469
|
-
await snapdom.toPng(el, {
|
|
470
|
-
embedFonts: true,
|
|
471
|
-
excludeFonts: {
|
|
472
|
-
families: ['Noto Serif', 'SomeHeavyFont'], // skip by family name
|
|
473
|
-
domains: ['fonts.gstatic.com', 'cdn.example'], // skip by source host
|
|
474
|
-
subsets: ['cyrillic-ext'] // skip by unicode-range subset tag
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
```
|
|
478
|
-
*Notes*
|
|
479
|
-
- `excludeFonts` only applies to **non-icon** fonts. Icon fonts are always embedded.
|
|
480
|
-
- Matching is case-insensitive for `families`. Hosts are matched by substring against the resolved URL.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
#### Filtering nodes: `exclude` vs `filter`
|
|
484
|
-
|
|
485
|
-
* `exclude`: remove by **selector**.
|
|
486
|
-
* `excludeMode`: `hide` applies `visibility:hidden` CSS rule on excluded nodes and the layout remains as the original. `remove` do not clone excluded nodes at all.
|
|
487
|
-
* `filter`: advanced predicate per element (return `false` to drop).
|
|
488
|
-
* `filterMode`: `hide` applies `visibility:hidden` CSS rule on filtered nodes and the layout remains as the original. `remove` do not clone filtered nodes at all.
|
|
489
|
-
|
|
490
|
-
**Example: filter out elements with `display:none`:**
|
|
491
|
-
```js
|
|
492
|
-
/**
|
|
493
|
-
* Example filter: skip elements with display:none
|
|
494
|
-
* @param {Element} el
|
|
495
|
-
* @returns {boolean} true = keep, false = exclude
|
|
496
|
-
*/
|
|
497
|
-
function filterHidden(el) {
|
|
498
|
-
const cs = window.getComputedStyle(el);
|
|
499
|
-
if (cs.display === 'none') return false;
|
|
500
|
-
return true;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
await snapdom.toPng(document.body, { filter: filterHidden });
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
**Example with `exclude`:** remove banners or tooltips by selector
|
|
507
|
-
```js
|
|
508
|
-
await snapdom.toPng(el, {
|
|
509
|
-
exclude: ['.cookie-banner', '.tooltip', '[data-test="debug"]']
|
|
510
|
-
});
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### outerTransforms
|
|
514
|
-
|
|
515
|
-
When capturing rotated or translated elements, you may want use **outerTransforms: false** option if you want to eliminate those external transforms. So, the output is **flat, upright, and ready** to use elsewhere.
|
|
516
|
-
|
|
517
|
-
- **`outerTransforms: true (default)`**
|
|
518
|
-
**Keeps the original `transforms` and `rotate`**.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
### outerShadows
|
|
522
|
-
- **`outerShadows: false (default)`**
|
|
523
|
-
Prevents expanding the bounding box for shadows, blur, or outline on the root, and also strips `box-shadow`, `text-shadow`, `filter: blur()/drop-shadow()`, and `outline` from the cloned root.
|
|
524
|
-
|
|
525
|
-
> 💡 **Tip:** Using both (`outerTransforms: false` + `outerShadows: false`) produces a strict, minimal bounding box with no visual bleed.
|
|
526
|
-
|
|
527
|
-
**Example**
|
|
528
|
-
|
|
529
|
-
```js
|
|
530
|
-
// outerTransforms and remove shadow bleed
|
|
531
|
-
await snapdom.toSvg(el, { outerTransforms: true, outerShadows: true });
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
## Cache control
|
|
535
|
-
|
|
536
|
-
SnapDOM maintains internal caches for images, backgrounds, resources, styles, and fonts.
|
|
537
|
-
You can control how they are cleared between captures using the `cache` option:
|
|
538
|
-
|
|
539
|
-
| Mode | Description |
|
|
540
|
-
| ----------- | --------------------------------------------------------------------------- |
|
|
541
|
-
| `"disabled"`| No cache |
|
|
542
|
-
| `"soft"` | Clears session caches (`styleMap`, `nodeMap`, `styleCache`) _(default)_ |
|
|
543
|
-
| `"auto"` | Minimal cleanup: only clears transient maps |
|
|
544
|
-
| `"full"` | Keeps all caches (nothing is cleared, maximum performance) |
|
|
545
|
-
|
|
546
|
-
**Examples:**
|
|
547
|
-
|
|
548
|
-
```js
|
|
549
|
-
// Use minimal but fast cache
|
|
550
|
-
await snapdom.toPng(el, { cache: 'auto' });
|
|
551
|
-
|
|
552
|
-
// Keep everything in memory between captures
|
|
553
|
-
await snapdom.toPng(el, { cache: 'full' });
|
|
554
|
-
|
|
555
|
-
// Force a full cleanup on every capture
|
|
556
|
-
await snapdom.toPng(el, { cache: 'disabled' });
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
## `preCache()` – Optional helper
|
|
560
|
-
|
|
561
|
-
Preloads external resources to avoid first-capture stalls (helpful for big/complex trees).
|
|
562
|
-
|
|
563
|
-
```js
|
|
564
|
-
import { preCache } from '@zumer/snapdom';
|
|
565
|
-
|
|
566
|
-
await preCache({
|
|
567
|
-
root: document.body,
|
|
568
|
-
embedFonts: true,
|
|
569
|
-
localFonts: [{ family: 'Inter', src: '/fonts/Inter.woff2', weight: 400 }],
|
|
570
|
-
useProxy: 'https://proxy.corsfix.com/?'
|
|
571
|
-
});
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
## Plugins (BETA)
|
|
575
|
-
|
|
576
|
-
SnapDOM includes a lightweight **plugin system** that allows you to extend or override behavior at any stage of the capture and export process — without touching the core library.
|
|
577
|
-
|
|
578
|
-
A plugin is a simple object with a unique `name` and one or more lifecycle **hooks**.
|
|
579
|
-
Hooks can be synchronous or `async`, and they receive a shared **`context`** object.
|
|
580
|
-
|
|
581
|
-
### Official Plugins
|
|
582
|
-
|
|
583
|
-
Install the official plugin package:
|
|
584
|
-
|
|
585
|
-
```bash
|
|
586
|
-
npm install @zumer/snapdom-plugins
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
```js
|
|
590
|
-
import { filter } from '@zumer/snapdom-plugins/filter';
|
|
591
|
-
import { timestampOverlay } from '@zumer/snapdom-plugins/timestamp-overlay';
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
| Plugin | Category | Description |
|
|
595
|
-
|--------|----------|-------------|
|
|
596
|
-
| `picture-resolver` | Capture | Resolves lazy-loaded `<picture>` placeholders. Detects base64 stubs and fetches the real image before capture. |
|
|
597
|
-
| `timestamp-overlay` | Transform | Adds a configurable timestamp label on the captured clone. Supports multiple date formats and positions. |
|
|
598
|
-
| `filter` | Transform | Applies CSS filter effects to captures. Ships with presets: `grayscale`, `sepia`, `blur`, `vintage`, `dramatic`. |
|
|
599
|
-
| `replace-text` | Transform | Find-and-replace text in the captured clone. Supports strings and regex patterns. |
|
|
600
|
-
| `color-tint` | Transform | Tints the entire capture to a specified color using an overlay with `mix-blend-mode`. |
|
|
601
|
-
| `ascii-export` | Export | Adds a `toAscii()` method that converts captures to ASCII art. Configurable width, charset, and luminance. |
|
|
602
|
-
| `pdf-image` | Export | Exports the capture as a PNG embedded in a downloadable PDF. Supports portrait and landscape orientations. |
|
|
603
|
-
| `html-in-canvas` | Export | Uses the experimental WICG `drawElementImage` API for direct DOM-to-canvas rendering where supported. |
|
|
604
|
-
| `prompt-export` | Export | LLM-friendly capture: adds a `toPrompt()` method that returns an annotated screenshot, structured element map with bounding boxes, and a pre-formatted text prompt. |
|
|
605
|
-
|
|
606
|
-
### Community Plugins
|
|
607
|
-
|
|
608
|
-
Community plugins are listed on the [Plugins page](https://zumerlab.github.io/snapdom/plugins.html). To submit your plugin, open a PR adding one line to `community-plugins.md`. See [CONTRIBUTING_PLUGINS.md](CONTRIBUTING_PLUGINS.md).
|
|
609
|
-
|
|
610
|
-
### Build a Plugin in 5 Minutes
|
|
611
|
-
|
|
612
|
-
SnapDOM's hook system gives you full control over every stage of the capture pipeline:
|
|
613
|
-
|
|
614
|
-
1. **Clone the template** — `npx degit zumerlab/snapdom/packages/plugin-template my-plugin`
|
|
615
|
-
2. **Write your hook logic** — `export function myPlugin() {}`
|
|
616
|
-
3. **Get listed** — open a PR adding one line to `community-plugins.md`
|
|
617
|
-
|
|
618
|
-
See [PLUGIN_SPEC.md](PLUGIN_SPEC.md) for the full specification and [CONTRIBUTING_PLUGINS.md](CONTRIBUTING_PLUGINS.md) for submission guidelines.
|
|
619
|
-
|
|
620
|
-
### Registering Plugins
|
|
621
|
-
|
|
622
|
-
**Global registration** (applies to all captures):
|
|
623
|
-
|
|
624
|
-
```js
|
|
625
|
-
import { snapdom } from '@zumer/snapdom';
|
|
626
|
-
|
|
627
|
-
// You can register instances, factories, or [factory, options]
|
|
628
|
-
snapdom.plugins(
|
|
629
|
-
myPluginInstance,
|
|
630
|
-
[myPluginFactory, { optionA: true }],
|
|
631
|
-
{ plugin: anotherFactory, options: { level: 2 } }
|
|
632
|
-
);
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
**Per-capture registration** (only for that specific call):
|
|
636
|
-
|
|
637
|
-
```js
|
|
638
|
-
const out = await snapdom(element, {
|
|
639
|
-
plugins: [
|
|
640
|
-
[overlayFilterPlugin, { color: 'rgba(0,0,0,0.25)' }],
|
|
641
|
-
[myFullPlugin, { providePdf: true }]
|
|
642
|
-
]
|
|
643
|
-
});
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
* **Execution order = registration order** (first registered, first executed).
|
|
647
|
-
* **Per-capture plugins** run **before** global ones.
|
|
648
|
-
* Duplicates are automatically skipped by `name`; a per-capture plugin with the same `name` overrides its global version.
|
|
649
|
-
|
|
650
|
-
### Plugin Lifecycle Hooks
|
|
651
|
-
|
|
652
|
-
Hooks run in capture order (see [Capture Flow](#capture-flow)):
|
|
653
|
-
|
|
654
|
-
| Hook | Stage | Purpose |
|
|
655
|
-
|------|-------|---------|
|
|
656
|
-
| `beforeSnap` | Start | Adjust options before any work. |
|
|
657
|
-
| `beforeClone` | Pre-clone | Before DOM clone (modify live DOM carefully). |
|
|
658
|
-
| `afterClone` | Post-clone | Modify cloned tree safely (e.g. inject overlay). |
|
|
659
|
-
| `beforeRender` | Pre-serialize | Right before SVG → data URL. |
|
|
660
|
-
| `afterRender` | Post-serialize | Inspect `context.svgString` / `context.dataURL`. |
|
|
661
|
-
| `beforeExport` | Per export | Before each `toPng`, `toSvg`, etc. |
|
|
662
|
-
| `afterExport` | Per export | Transform returned result. |
|
|
663
|
-
| `afterSnap` | Once | After first export; cleanup. |
|
|
664
|
-
| `defineExports` | Setup | Add custom exporters (e.g. `toPdf`). |
|
|
665
|
-
|
|
666
|
-
> Returned values from `afterExport` are chained to the next plugin (transform pipeline).
|
|
667
|
-
|
|
668
|
-
### Context Object
|
|
669
|
-
|
|
670
|
-
Every hook receives a single `context` object that contains normalized capture state:
|
|
671
|
-
|
|
672
|
-
* **Input & options:**
|
|
673
|
-
`element`, `debug`, `fast`, `scale`, `dpr`, `width`, `height`, `backgroundColor`, `quality`, `useProxy`, `cache`, `outerTransforms`, `outerShadows`, `safariWarmupAttempts`, `compress`, `embedFonts`, `localFonts`, `iconFonts`, `excludeFonts`, `exclude`, `excludeMode`, `filter`, `filterMode`, `fallbackURL`.
|
|
674
|
-
|
|
675
|
-
* **Intermediate values (depending on stage):**
|
|
676
|
-
`clone`, `classCSS`, `styleCache`, `fontsCSS`, `baseCSS`, `svgString`, `dataURL`.
|
|
677
|
-
|
|
678
|
-
* **During export:**
|
|
679
|
-
`context.export = { type, options, url }`
|
|
680
|
-
where `type` is the exporter name (`"png"`, `"jpeg"`, `"svg"`, `"blob"`, etc.), and `url` is the serialized SVG base.
|
|
681
|
-
|
|
682
|
-
> You may safely modify `context` (e.g., override `backgroundColor` or `quality`) — but do so early (`beforeSnap`) for global effects or in `beforeExport` for single-export changes.
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
## Custom Exports via Plugins
|
|
686
|
-
|
|
687
|
-
Plugins can add new exports using `defineExports(context)`.
|
|
688
|
-
For each export key you return (e.g., `"pdf"`), SnapDOM automatically exposes a helper method named **`toPdf()`** on the capture result.
|
|
689
|
-
|
|
690
|
-
**Register the plugin (global or per capture):**
|
|
691
|
-
|
|
692
|
-
```js
|
|
693
|
-
import { snapdom } from '@zumer/snapdom';
|
|
694
|
-
|
|
695
|
-
// global
|
|
696
|
-
snapdom.plugins(pdfExportPlugin());
|
|
697
|
-
|
|
698
|
-
// or per capture
|
|
699
|
-
const out = await snapdom(element, { plugins: [pdfExportPlugin()] });
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
**Call the custom export:**
|
|
703
|
-
|
|
704
|
-
```js
|
|
705
|
-
const out = await snapdom(document.querySelector('#report'));
|
|
706
|
-
|
|
707
|
-
// because the plugin returns { pdf: async (ctx, opts) => ... }
|
|
708
|
-
const pdfBlob = await out.toPdf({
|
|
709
|
-
// exporter-specific options (width, height, quality, filename, etc.)
|
|
710
|
-
});
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
### Example: Overlay Filter Plugin
|
|
714
|
-
|
|
715
|
-
Adds a translucent overlay or color filter **only** to the captured clone (not your live DOM).
|
|
716
|
-
Useful for highlighting or dimming sections before export.
|
|
717
|
-
|
|
718
|
-
```js
|
|
719
|
-
/**
|
|
720
|
-
* Ultra-simple overlay filter for SnapDOM (HTML-only).
|
|
721
|
-
* Inserts a full-size <div> overlay on the cloned root.
|
|
722
|
-
*
|
|
723
|
-
* @param {{ color?: string; blur?: number }} [options]
|
|
724
|
-
* color: overlay color (rgba/hex/hsl). Default: 'rgba(0,0,0,0.25)'
|
|
725
|
-
* blur: optional blur in px (default: 0)
|
|
726
|
-
*/
|
|
727
|
-
export function overlayFilterPlugin(options = {}) {
|
|
728
|
-
const color = options.color ?? 'rgba(0,0,0,0.25)';
|
|
729
|
-
const blur = Math.max(0, options.blur ?? 0);
|
|
730
|
-
|
|
731
|
-
return {
|
|
732
|
-
name: 'overlay-filter',
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* Add a full-coverage overlay to the cloned HTML root.
|
|
736
|
-
* @param {any} context
|
|
737
|
-
*/
|
|
738
|
-
async afterClone(context) {
|
|
739
|
-
const root = context.clone;
|
|
740
|
-
if (!(root instanceof HTMLElement)) return; // HTML-only
|
|
741
|
-
|
|
742
|
-
// Ensure containing block so absolute overlay anchors to the root
|
|
743
|
-
if (getComputedStyle(root).position === 'static') {
|
|
744
|
-
root.style.position = 'relative';
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
const overlay = document.createElement('div');
|
|
748
|
-
overlay.style.position = 'absolute';
|
|
749
|
-
overlay.style.left = '0';
|
|
750
|
-
overlay.style.top = '0';
|
|
751
|
-
overlay.style.right = '0';
|
|
752
|
-
overlay.style.bottom = '0';
|
|
753
|
-
overlay.style.background = color;
|
|
754
|
-
overlay.style.pointerEvents = 'none';
|
|
755
|
-
if (blur) overlay.style.filter = `blur(${blur}px)`;
|
|
756
|
-
|
|
757
|
-
root.appendChild(overlay);
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
**Usage:**
|
|
765
|
-
|
|
766
|
-
```js
|
|
767
|
-
import { snapdom } from '@zumer/snapdom';
|
|
768
|
-
|
|
769
|
-
// Global registration
|
|
770
|
-
snapdom.plugins([overlayFilterPlugin, { color: 'rgba(0,0,0,0.3)', blur: 2 }]);
|
|
771
|
-
|
|
772
|
-
// Per-capture
|
|
773
|
-
const out = await snapdom(document.querySelector('#card'), {
|
|
774
|
-
plugins: [[overlayFilterPlugin, { color: 'rgba(255,200,0,0.15)' }]]
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
const png = await out.toPng();
|
|
778
|
-
document.body.appendChild(png);
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
> The overlay is injected **only in the cloned tree**, never in your live DOM, ensuring perfect fidelity and zero flicker.
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
### Full Plugin Template
|
|
785
|
-
|
|
786
|
-
Use this as a starting point for custom logic or exporters.
|
|
787
|
-
|
|
788
|
-
```js
|
|
789
|
-
export function myPlugin(options = {}) {
|
|
790
|
-
return {
|
|
791
|
-
/** Unique name used for de-duplication/overrides */
|
|
792
|
-
name: 'my-plugin',
|
|
793
|
-
|
|
794
|
-
/** Early adjustments before any clone/style work. */
|
|
795
|
-
async beforeSnap(context) {},
|
|
796
|
-
|
|
797
|
-
/** Before subtree cloning (use sparingly if touching the live DOM). */
|
|
798
|
-
async beforeClone(context) {},
|
|
799
|
-
|
|
800
|
-
/** After subtree cloning (safe to modify the cloned tree). */
|
|
801
|
-
async afterClone(context) {},
|
|
802
|
-
|
|
803
|
-
/** Right before serialization (SVG/dataURL). */
|
|
804
|
-
async beforeRender(context) {},
|
|
805
|
-
|
|
806
|
-
/** After serialization; inspect context.svgString/context.dataURL if needed. */
|
|
807
|
-
async afterRender(context) {},
|
|
808
|
-
|
|
809
|
-
/** Before EACH export call (toPng/toSvg/toBlob/...). */
|
|
810
|
-
async beforeExport(context) {},
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* After EACH export call.
|
|
814
|
-
* If you return a value, it becomes the result for the next plugin (chaining).
|
|
815
|
-
*/
|
|
816
|
-
async afterExport(context, result) { return result; },
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* Define custom exporters (auto-added as helpers like out.toPdf()).
|
|
820
|
-
* Return a map { [key: string]: (ctx:any, opts:any) => Promise<any> }.
|
|
821
|
-
*/
|
|
822
|
-
async defineExports(context) { return {}; },
|
|
823
|
-
|
|
824
|
-
/** Runs ONCE after the FIRST export finishes (cleanup). */
|
|
825
|
-
async afterSnap(context) {}
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
**Quick recap:**
|
|
831
|
-
|
|
832
|
-
* Plugins can modify capture behavior (`beforeSnap`, `afterClone`, etc.).
|
|
833
|
-
* You can inject visuals or transformations safely into the cloned tree.
|
|
834
|
-
* New exporters defined in `defineExports()` automatically become helpers like `out.toPdf()`.
|
|
835
|
-
* All hooks can be asynchronous, run in order, and share the same `context`.
|
|
251
|
+
| `snapdom.download(el, options?)` | Triggers a download |
|
|
836
252
|
|
|
253
|
+
📖 **[Full API & every option → snapdom.dev/docs](https://snapdom.dev/docs/)**
|
|
837
254
|
|
|
838
255
|
## Limitations
|
|
839
256
|
|