@zumer/snapdom 2.15.0-dev.1 → 2.16.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 CHANGED
@@ -1,11 +1,14 @@
1
1
  <p align="center">
2
- <a href="http://zumerlab.github.io/snapdom">
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://www.npmjs.com/package/@zumer/snapdom">
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,16 +39,15 @@
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
 
39
- * Full DOM capture
40
- * Embedded styles, pseudo-elements, and fonts
41
- * Export to SVG, PNG, JPG, WebP, `canvas`, or Blob
42
- * ⚡ Ultra fast, no dependencies
43
- * 100% based on standard Web APIs
44
- * Support same-origin `ìframe`
45
- * Support CSS counter() and CSS counters()
46
- * Support `...` line-clamp
42
+ > 📖 **[Documentation, guides & live demos → snapdom.dev](https://snapdom.dev)**
43
+
44
+ ## Features
45
+
46
+ Full DOM capture with embedded styles, pseudo-elements and fonts; export to SVG, PNG, JPG, WebP, `canvas` or Blob — ultra fast, dependency-free, and 100% based on standard Web APIs.
47
47
 
48
- ## Demo
48
+ 👉 **See the complete technical feature list in [FEATURES.md](FEATURES.md).**
49
+
50
+ ## Website & Live Demos
49
51
 
50
52
  [https://snapdom.dev](https://snapdom.dev)
51
53
 
@@ -72,95 +74,24 @@ await result.download({ format: 'jpg', filename: 'card.jpg' });
72
74
 
73
75
  ---
74
76
 
75
- ## Capture Flow
76
-
77
- SnapDOM transforms your DOM element through these stages:
78
-
79
- ```
80
- DOM Element
81
- ↓
82
- Clone
83
- ↓
84
- Styles & Pseudo
85
- ↓
86
- Images & Backgrounds
87
- ↓
88
- Fonts
89
- ↓
90
- SVG foreignObject
91
- ↓
92
- data:image/svg+xml
93
- ↓
94
- toPng / toSvg / toBlob / download
95
- ```
96
-
97
- | Stage | What happens |
98
- |-------|--------------|
99
- | **Clone** | Deep clone with styles, Shadow DOM, iframes. Exclude/filter nodes. |
100
- | **Styles & Pseudo** | Inline `::before`/`::after` as elements, resolve `counter()`/`counters()`. |
101
- | **Images & Backgrounds** | Fetch and inline external images/backgrounds as data URLs. |
102
- | **Fonts** | Embed `@font-face` (optional) and icon fonts. |
103
- | **SVG** | Wrap clone in `<foreignObject>`, serialize to `data:image/svg+xml`. |
104
- | **Export** | Convert SVG to PNG/JPG/WebP/Blob or trigger download. |
105
-
106
- Plugin hooks: `beforeSnap` → `beforeClone` → `afterClone` → `beforeRender` → `afterRender` → `beforeExport` → `afterExport`.
107
-
108
-
109
77
  ## Table of Contents
110
78
 
111
79
  - [Quick Start](#quick-start)
112
- - [Capture Flow](#capture-flow)
80
+ - [Features](#features)
81
+ - [Website & Live Demos](#website--live-demos)
113
82
  - [Installation](#installation)
114
- - [NPM / Yarn (stable)](#npm--yarn-stable)
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)
83
+ - [Build Outputs](#build-outputs)
119
84
  - [Usage](#usage)
120
- - [Reusable capture](#reusable-capture)
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)
85
+ - [Documentation](#documentation) — full API, Options, Plugins & Cache reference on [snapdom.dev/docs](https://snapdom.dev/docs/)
150
86
  - [Limitations](#limitations)
151
- - [⚡ Performance Benchmarks (Chromium)](#performance-benchmarks)
152
- - [Simple elements](#simple-elements)
153
- - [Complex elements](#complex-elements)
154
- - [Run the benchmarks](#run-the-benchmarks)
155
- - [Roadmap](#roadmap)
87
+ - [Performance Benchmarks](#performance-benchmarks)
156
88
  - [Development](#development)
157
- - [Contributors 🙌](#contributors)
158
- - [💖 Sponsors](#sponsors)
89
+ - [Contributors](#contributors)
90
+ - [Sponsors](#sponsors)
91
+ - [Show your support](#show-your-support)
159
92
  - [Star History](#star-history)
160
93
  - [License](#license)
161
94
 
162
-
163
-
164
95
  ## Installation
165
96
 
166
97
  ### NPM / Yarn (stable)
@@ -261,579 +192,30 @@ const blob = await snapdom.toBlob(el);
261
192
  document.body.appendChild(png);
262
193
  ```
263
194
 
264
- ## API
195
+ ## Documentation
265
196
 
266
- ### `snapdom(el, options?)`
197
+ The full reference lives on **[snapdom.dev/docs](https://snapdom.dev/docs/)** — kept there so it stays in sync and searchable:
267
198
 
268
- Returns an object with reusable export methods:
199
+ - **[API reference](https://snapdom.dev/docs/api/)** — the `snapdom()` reusable object, shortcut methods, and exporter-specific options.
200
+ - **[Options](https://snapdom.dev/docs/options/)** — every capture option (`scale`, `dpr`, `embedFonts`, `useProxy`, `exclude`/`filter`, `compress`, `outerTransforms`, `outerShadows`, `cache`…) explained with examples.
201
+ - **[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).
202
+ - **[Cache & preCache](https://snapdom.dev/docs/cache/)** — control caching between captures and preload resources.
269
203
 
270
- ```js
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
- ```
204
+ ### API at a glance
284
205
 
285
- ### Shortcut methods
206
+ `snapdom(el, options?)` returns a reusable object (`toPng`, `toSvg`, `toCanvas`, `toBlob`, `toJpg`, `toWebp`, `download`, `url`). For single exports, use the shortcuts:
286
207
 
287
- | Method | Description |
208
+ | Method | Description |
288
209
  | ------------------------------ | --------------------------------- |
289
- | `snapdom.toImg(el, options?)` | Returns an SVG `HTMLImageElement` (deprecated) |
290
210
  | `snapdom.toSvg(el, options?)` | Returns an SVG `HTMLImageElement` |
291
- | `snapdom.toCanvas(el, options?)` | Returns a `Canvas` |
211
+ | `snapdom.toCanvas(el, options?)` | Returns a `Canvas` |
292
212
  | `snapdom.toBlob(el, options?)` | Returns an SVG or raster `Blob` |
293
213
  | `snapdom.toPng(el, options?)` | Returns a PNG image |
294
214
  | `snapdom.toJpg(el, options?)` | Returns a JPG image |
295
215
  | `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`.
216
+ | `snapdom.download(el, options?)` | Triggers a download |
836
217
 
218
+ 📖 **[Full API & every option → snapdom.dev/docs](https://snapdom.dev/docs/)**
837
219
 
838
220
  ## Limitations
839
221
 
@@ -881,35 +263,6 @@ npm run test:benchmark
881
263
  ```
882
264
 
883
265
 
884
- ## Roadmap
885
-
886
- Planned improvements for future versions of SnapDOM:
887
-
888
- * [X] **Implement plugin system**
889
- SnapDOM will support external plugins to extend or override internal behavior (e.g. custom node transformers, exporters, or filters).
890
-
891
- * [ ] **Refactor to modular architecture**
892
- Internal logic will be split into smaller, focused modules to improve maintainability and code reuse.
893
-
894
- * [X] **Decouple internal logic from global options**
895
- Functions will be redesigned to avoid relying directly on `options`. A centralized capture context will improve clarity, autonomy, and testability. See [`next` branch](https://github.com/zumerlab/snapdom/tree/main)
896
-
897
- * [X] **Expose cache control**
898
- Users will be able to manually clear image and font caches or configure their own caching strategies.
899
-
900
- * [X] **Auto font preloading**
901
- Required fonts will be automatically detected and preloaded before capture, reducing the need for manual `preCache()` calls.
902
-
903
- * [X] **Document plugin development**
904
- A full guide will be provided for creating and registering custom SnapDOM plugins.
905
-
906
- * [ ] **Make export utilities tree-shakeable**
907
- Export functions like `toPng`, `toJpg`, `toBlob`, etc. will be restructured into independent modules to support tree shaking and minimal builds.
908
-
909
- Have ideas or feature requests?
910
- Feel free to share suggestions or feedback in [GitHub Discussions](https://github.com/zumerlab/snapdom/discussions).
911
-
912
-
913
266
  ## Development
914
267
 
915
268
  **Source layout:**
@@ -979,6 +332,35 @@ Special thanks to [@megaphonecolin](https://github.com/megaphonecolin), [@sdrape
979
332
 
980
333
  If you'd like to support this project too, you can [become a sponsor](https://github.com/sponsors/tinchox5).
981
334
 
335
+ ## Show your support
336
+
337
+ If snapDOM saved you time, a ⭐ on GitHub helps other developers find it — that's the whole ask.
338
+
339
+ Shipping something built with snapDOM? Add the badge to your README:
340
+
341
+ [![Built with snapDOM](https://img.shields.io/badge/built%20with-snapDOM-blue)](https://snapdom.dev)
342
+
343
+ ```md
344
+ [![Built with snapDOM](https://img.shields.io/badge/built%20with-snapDOM-blue)](https://snapdom.dev)
345
+ ```
346
+
347
+ ### Projects using snapDOM
348
+
349
+ snapDOM runs in production across 290+ public repositories ([GitHub dependents graph](https://github.com/zumerlab/snapdom/network/dependents)). A few notable ones, each verified from its own `package.json`:
350
+
351
+ - [LobeHub](https://github.com/lobehub/lobehub) — platform for operating AI agents
352
+ - [Trilium Notes](https://github.com/TriliumNext/Trilium) — hierarchical personal knowledge base
353
+ - [Sealos](https://github.com/labring/sealos) — AI-native cloud operating system
354
+ - [Tencent tmagic-editor](https://github.com/Tencent/tmagic-editor) — low-code page editor
355
+ - [Playroom](https://github.com/seek-oss/playroom) — JSX design tool by SEEK
356
+ - [GPT-Vis](https://github.com/antvis/GPT-Vis) — AI-friendly data viz by Ant Group's AntV
357
+ - [Rabby Wallet](https://github.com/RabbyHub/Rabby) — browser wallet for EVM chains
358
+ - [uMap](https://github.com/umap-project/umap) — OpenStreetMap map builder
359
+ - [ListenBrainz](https://github.com/metabrainz/listenbrainz-server) — music tracker by MetaBrainz
360
+ - [SnapDIFF](https://zumerlab.com/snapdiff/) — in-browser visual regression testing *(by Zumerlab)*
361
+
362
+ 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.
363
+
982
364
  ## Star History
983
365
 
984
366
  [![Star History Chart](https://api.star-history.com/svg?repos=zumerlab/snapdom&type=Date)](https://www.star-history.com/#zumerlab/snapdom&Date)