@zumer/snapdom 1.9.14 → 2.0.0-dev.1
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 +253 -22
- package/dist/snapdom.js +771 -231
- package/dist/snapdom.min.js +7 -7
- package/dist/snapdom.min.mjs +7 -7
- package/dist/snapdom.mjs +733 -227
- package/package.json +1 -1
- package/types/snapdom.d.ts +269 -176
package/README.md
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
<a href="https://www.npmjs.com/package/@zumer/snapdom">
|
|
9
9
|
<img alt="NPM version" src="https://img.shields.io/npm/v/@zumer/snapdom?style=flat-square&label=Version">
|
|
10
10
|
</a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@zumer/snapdom">
|
|
12
|
+
<img alt="NPM version" src="https://img.shields.io/npm/v/@zumer/snapdom/dev?style=flat-square&label=Dev">
|
|
13
|
+
</a>
|
|
11
14
|
<a href="https://github.com/zumerlab/snapdom/graphs/contributors">
|
|
12
15
|
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/zumerlab/snapdom?style=flat-square&label=Contributors">
|
|
13
16
|
</a>
|
|
@@ -32,11 +35,11 @@
|
|
|
32
35
|
|
|
33
36
|
It captures any HTML element as a scalable SVG image, preserving styles, fonts, background images, pseudo-elements, and even shadow DOM. It also supports export to raster image formats and canvas.
|
|
34
37
|
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
+
* Full DOM capture
|
|
39
|
+
* Embedded styles, pseudo-elements, and fonts
|
|
40
|
+
* Export to SVG, PNG, JPG, WebP, `canvas`, or Blob
|
|
38
41
|
* ⚡ Ultra fast, no dependencies
|
|
39
|
-
*
|
|
42
|
+
* 100% based on standard Web APIs
|
|
40
43
|
* Support same-origin `ìframe`
|
|
41
44
|
* Support CSS counter() and CSS counters()
|
|
42
45
|
* Support `...` line-clamp
|
|
@@ -69,10 +72,17 @@ It captures any HTML element as a scalable SVG image, preserving styles, fonts,
|
|
|
69
72
|
- [iconFonts](#iconfonts)
|
|
70
73
|
- [excludeFonts](#excludefonts)
|
|
71
74
|
- [Filtering nodes: `exclude` vs `filter`](#filtering-nodes-exclude-vs-filter)
|
|
72
|
-
- [
|
|
73
|
-
- [
|
|
75
|
+
- [outerTransforms](#outerTransforms)
|
|
76
|
+
- [outerShadows](#no-shadows)
|
|
74
77
|
- [Cache control](#cache-control)
|
|
75
78
|
- [preCache](#precache--optional-helper)
|
|
79
|
+
- [Plugins (BETA)](#plugins-beta)
|
|
80
|
+
- [Registering Plugins](#registering-plugins)
|
|
81
|
+
- [Plugin Lifecycle Hooks](#plugin-lifecycle-hooks)
|
|
82
|
+
- [Context Object](#context-object)
|
|
83
|
+
- [Custom Exports via Plugins](#custom-exports-via-plugins)
|
|
84
|
+
- [Example: Overlay Filter Plugin](#example-overlay-filter-plugin)
|
|
85
|
+
- [Full Plugin Template](#full-plugin-template)
|
|
76
86
|
- [Limitations](#limitations)
|
|
77
87
|
- [⚡ Performance Benchmarks (Chromium)](#performance-benchmarks)
|
|
78
88
|
- [Simple elements](#simple-elements)
|
|
@@ -218,8 +228,8 @@ All capture methods accept an `options` object:
|
|
|
218
228
|
| `cache` | string | `"soft"` | `disabled` \| `soft` \| `auto` \| `full` |
|
|
219
229
|
| `placeholders` | boolean | `true` | Show placeholders for images/CORS iframes |
|
|
220
230
|
| `fallbackURL` | string \| function | - | Fallback image for `<img>` load failure |
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
231
|
+
| `outerTransforms` | boolean | `true` | When `false` removes `translate/rotate` but preserves `scale/skew`, producing a flat, reusable capture |
|
|
232
|
+
| `outerShadows` | boolean | `false` | Do not expand the root’s bounding box for shadows/blur/outline, and strip those visual effects from the cloned root |
|
|
223
233
|
|
|
224
234
|
### Fallback image on `<img>` load failure
|
|
225
235
|
|
|
@@ -344,26 +354,25 @@ await snapdom.toPng(el, {
|
|
|
344
354
|
});
|
|
345
355
|
```
|
|
346
356
|
|
|
347
|
-
###
|
|
348
|
-
|
|
349
|
-
When capturing rotated or translated elements, you may want to **straighten** the root so the snapshot can be reused in another layout without inheriting those transforms.
|
|
357
|
+
### outerTransforms
|
|
350
358
|
|
|
351
|
-
|
|
352
|
-
Straightens the cloned root: **removes `translate` and `rotate`** but **keeps `scale/skew`** to preserve proportions.
|
|
353
|
-
The output is **flat, upright, and ready** to embed elsewhere.
|
|
359
|
+
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.
|
|
354
360
|
|
|
361
|
+
- **`outerTransforms: true (default)`**
|
|
362
|
+
**Keeps the original `transforms` and `rotate`**.
|
|
363
|
+
|
|
355
364
|
|
|
356
|
-
###
|
|
357
|
-
- **`
|
|
365
|
+
### outerShadows
|
|
366
|
+
- **`outerShadows: false (default)`**
|
|
358
367
|
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.
|
|
359
368
|
|
|
360
|
-
> 💡 **Tip:** Using both (`
|
|
369
|
+
> 💡 **Tip:** Using both (`outerTransforms: false` + `outerShadows: false`) produces a strict, minimal bounding box with no visual bleed.
|
|
361
370
|
|
|
362
371
|
**Example**
|
|
363
372
|
|
|
364
373
|
```js
|
|
365
|
-
//
|
|
366
|
-
await snapdom.toSvg(el, {
|
|
374
|
+
// outerTransforms and remove shadow bleed
|
|
375
|
+
await snapdom.toSvg(el, { outerTransforms: true, outerShadows: true });
|
|
367
376
|
```
|
|
368
377
|
|
|
369
378
|
## Cache control
|
|
@@ -406,6 +415,229 @@ await preCache({
|
|
|
406
415
|
});
|
|
407
416
|
```
|
|
408
417
|
|
|
418
|
+
## Plugins (BETA)
|
|
419
|
+
|
|
420
|
+
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.
|
|
421
|
+
|
|
422
|
+
A plugin is a simple object with a unique `name` and one or more lifecycle **hooks**.
|
|
423
|
+
Hooks can be synchronous or `async`, and they receive a shared **`context`** object.
|
|
424
|
+
|
|
425
|
+
### Registering Plugins
|
|
426
|
+
|
|
427
|
+
**Global registration** (applies to all captures):
|
|
428
|
+
|
|
429
|
+
```js
|
|
430
|
+
import { snapdom } from '@zumer/snapdom';
|
|
431
|
+
|
|
432
|
+
// You can register instances, factories, or [factory, options]
|
|
433
|
+
snapdom.plugins(
|
|
434
|
+
myPluginInstance,
|
|
435
|
+
[myPluginFactory, { optionA: true }],
|
|
436
|
+
{ plugin: anotherFactory, options: { level: 2 } }
|
|
437
|
+
);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Per-capture registration** (only for that specific call):
|
|
441
|
+
|
|
442
|
+
```js
|
|
443
|
+
const out = await snapdom(element, {
|
|
444
|
+
plugins: [
|
|
445
|
+
[overlayFilterPlugin, { color: 'rgba(0,0,0,0.25)' }],
|
|
446
|
+
[myFullPlugin, { providePdf: true }]
|
|
447
|
+
]
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
* **Execution order = registration order** (first registered, first executed).
|
|
452
|
+
* **Per-capture plugins** run **before** global ones.
|
|
453
|
+
* Duplicates are automatically skipped by `name`; a per-capture plugin with the same `name` overrides its global version.
|
|
454
|
+
|
|
455
|
+
### Plugin Lifecycle Hooks
|
|
456
|
+
|
|
457
|
+
| Hook | Purpose |
|
|
458
|
+
| ------------------------------ | ------------------------------------------------------------------------------------ |
|
|
459
|
+
| `beforeSnap(context)` | Before any clone/style work. Ideal for adjusting global capture options. |
|
|
460
|
+
| `beforeClone(context)` | Before DOM cloning. Can modify live DOM (use carefully). |
|
|
461
|
+
| `afterClone(context)` | After the element subtree has been cloned. Safe to modify styles in the cloned tree. |
|
|
462
|
+
| `beforeRender(context)` | Right before SVG/dataURL serialization. |
|
|
463
|
+
| `afterRender(context)` | After serialization (you can inspect `context.svgString` or `context.dataURL`). |
|
|
464
|
+
| `beforeExport(context)` | Before each export call (`toPng`, `toSvg`, etc.). |
|
|
465
|
+
| `afterExport(context, result)` | After each export call — can transform the returned result. |
|
|
466
|
+
| `afterSnap(context)` | Runs **once**, after the **first export** finishes. Perfect for cleanup. |
|
|
467
|
+
| `defineExports(context)` | Returns a map of **custom exporters**, e.g. `{ pdf: async (ctx, opts) => Blob }`. |
|
|
468
|
+
|
|
469
|
+
> Returned values from `afterExport` are chained to the next plugin (transform pipeline).
|
|
470
|
+
|
|
471
|
+
### Context Object
|
|
472
|
+
|
|
473
|
+
Every hook receives a single `context` object that contains normalized capture state:
|
|
474
|
+
|
|
475
|
+
* **Input & options:**
|
|
476
|
+
`element`, `debug`, `fast`, `scale`, `dpr`, `width`, `height`, `backgroundColor`, `quality`, `useProxy`, `cache`, `outerTransforms`, `outerShadows`, `embedFonts`, `localFonts`, `iconFonts`, `excludeFonts`, `exclude`, `excludeMode`, `filter`, `filterMode`, `fallbackURL`.
|
|
477
|
+
|
|
478
|
+
* **Intermediate values (depending on stage):**
|
|
479
|
+
`clone`, `classCSS`, `styleCache`, `fontsCSS`, `baseCSS`, `svgString`, `dataURL`.
|
|
480
|
+
|
|
481
|
+
* **During export:**
|
|
482
|
+
`context.export = { type, options, url }`
|
|
483
|
+
where `type` is the exporter name (`"png"`, `"jpeg"`, `"svg"`, `"blob"`, etc.), and `url` is the serialized SVG base.
|
|
484
|
+
|
|
485
|
+
> 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.
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
## Custom Exports via Plugins
|
|
489
|
+
|
|
490
|
+
Plugins can add new exports using `defineExports(context)`.
|
|
491
|
+
For each export key you return (e.g., `"pdf"`), SnapDOM automatically exposes a helper method named **`toPdf()`** on the capture result.
|
|
492
|
+
|
|
493
|
+
**Register the plugin (global or per capture):**
|
|
494
|
+
|
|
495
|
+
```js
|
|
496
|
+
import { snapdom } from '@zumer/snapdom';
|
|
497
|
+
|
|
498
|
+
// global
|
|
499
|
+
snapdom.plugins(pdfExportPlugin());
|
|
500
|
+
|
|
501
|
+
// or per capture
|
|
502
|
+
const out = await snapdom(element, { plugins: [pdfExportPlugin()] });
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Call the custom export:**
|
|
506
|
+
|
|
507
|
+
```js
|
|
508
|
+
const out = await snapdom(document.querySelector('#report'));
|
|
509
|
+
|
|
510
|
+
// because the plugin returns { pdf: async (ctx, opts) => ... }
|
|
511
|
+
const pdfBlob = await out.toPdf({
|
|
512
|
+
// exporter-specific options (width, height, quality, filename, etc.)
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Example: Overlay Filter Plugin
|
|
517
|
+
|
|
518
|
+
Adds a translucent overlay or color filter **only** to the captured clone (not your live DOM).
|
|
519
|
+
Useful for highlighting or dimming sections before export.
|
|
520
|
+
|
|
521
|
+
```js
|
|
522
|
+
/**
|
|
523
|
+
* Ultra-simple overlay filter for SnapDOM (HTML-only).
|
|
524
|
+
* Inserts a full-size <div> overlay on the cloned root.
|
|
525
|
+
*
|
|
526
|
+
* @param {{ color?: string; blur?: number }} [options]
|
|
527
|
+
* color: overlay color (rgba/hex/hsl). Default: 'rgba(0,0,0,0.25)'
|
|
528
|
+
* blur: optional blur in px (default: 0)
|
|
529
|
+
*/
|
|
530
|
+
export function overlayFilterPlugin(options = {}) {
|
|
531
|
+
const color = options.color ?? 'rgba(0,0,0,0.25)';
|
|
532
|
+
const blur = Math.max(0, options.blur ?? 0);
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
name: 'overlay-filter',
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Add a full-coverage overlay to the cloned HTML root.
|
|
539
|
+
* @param {any} context
|
|
540
|
+
*/
|
|
541
|
+
async afterClone(context) {
|
|
542
|
+
const root = context.clone;
|
|
543
|
+
if (!(root instanceof HTMLElement)) return; // HTML-only
|
|
544
|
+
|
|
545
|
+
// Ensure containing block so absolute overlay anchors to the root
|
|
546
|
+
if (getComputedStyle(root).position === 'static') {
|
|
547
|
+
root.style.position = 'relative';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const overlay = document.createElement('div');
|
|
551
|
+
overlay.style.position = 'absolute';
|
|
552
|
+
overlay.style.left = '0';
|
|
553
|
+
overlay.style.top = '0';
|
|
554
|
+
overlay.style.right = '0';
|
|
555
|
+
overlay.style.bottom = '0';
|
|
556
|
+
overlay.style.background = color;
|
|
557
|
+
overlay.style.pointerEvents = 'none';
|
|
558
|
+
if (blur) overlay.style.filter = `blur(${blur}px)`;
|
|
559
|
+
|
|
560
|
+
root.appendChild(overlay);
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Usage:**
|
|
568
|
+
|
|
569
|
+
```js
|
|
570
|
+
import { snapdom } from '@zumer/snapdom';
|
|
571
|
+
|
|
572
|
+
// Global registration
|
|
573
|
+
snapdom.plugins([overlayFilterPlugin, { color: 'rgba(0,0,0,0.3)', blur: 2 }]);
|
|
574
|
+
|
|
575
|
+
// Per-capture
|
|
576
|
+
const out = await snapdom(document.querySelector('#card'), {
|
|
577
|
+
plugins: [[overlayFilterPlugin, { color: 'rgba(255,200,0,0.15)' }]]
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const png = await out.toPng();
|
|
581
|
+
document.body.appendChild(png);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
> The overlay is injected **only in the cloned tree**, never in your live DOM, ensuring perfect fidelity and zero flicker.
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
### Full Plugin Template
|
|
588
|
+
|
|
589
|
+
Use this as a starting point for custom logic or exporters.
|
|
590
|
+
|
|
591
|
+
```js
|
|
592
|
+
export function myPlugin(options = {}) {
|
|
593
|
+
return {
|
|
594
|
+
/** Unique name used for de-duplication/overrides */
|
|
595
|
+
name: 'my-plugin',
|
|
596
|
+
|
|
597
|
+
/** Early adjustments before any clone/style work. */
|
|
598
|
+
async beforeSnap(context) {},
|
|
599
|
+
|
|
600
|
+
/** Before subtree cloning (use sparingly if touching the live DOM). */
|
|
601
|
+
async beforeClone(context) {},
|
|
602
|
+
|
|
603
|
+
/** After subtree cloning (safe to modify the cloned tree). */
|
|
604
|
+
async afterClone(context) {},
|
|
605
|
+
|
|
606
|
+
/** Right before serialization (SVG/dataURL). */
|
|
607
|
+
async beforeRender(context) {},
|
|
608
|
+
|
|
609
|
+
/** After serialization; inspect context.svgString/context.dataURL if needed. */
|
|
610
|
+
async afterRender(context) {},
|
|
611
|
+
|
|
612
|
+
/** Before EACH export call (toPng/toSvg/toBlob/...). */
|
|
613
|
+
async beforeExport(context) {},
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* After EACH export call.
|
|
617
|
+
* If you return a value, it becomes the result for the next plugin (chaining).
|
|
618
|
+
*/
|
|
619
|
+
async afterExport(context, result) { return result; },
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Define custom exporters (auto-added as helpers like out.toPdf()).
|
|
623
|
+
* Return a map { [key: string]: (ctx:any, opts:any) => Promise<any> }.
|
|
624
|
+
*/
|
|
625
|
+
async defineExports(context) { return {}; },
|
|
626
|
+
|
|
627
|
+
/** Runs ONCE after the FIRST export finishes (cleanup). */
|
|
628
|
+
async afterSnap(context) {}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**Quick recap:**
|
|
634
|
+
|
|
635
|
+
* Plugins can modify capture behavior (`beforeSnap`, `afterClone`, etc.).
|
|
636
|
+
* You can inject visuals or transformations safely into the cloned tree.
|
|
637
|
+
* New exporters defined in `defineExports()` automatically become helpers like `out.toPdf()`.
|
|
638
|
+
* All hooks can be asynchronous, run in order, and share the same `context`.
|
|
639
|
+
|
|
640
|
+
|
|
409
641
|
## Limitations
|
|
410
642
|
|
|
411
643
|
* External images should be CORS-accessible (use `useProxy` option for handling CORS denied)
|
|
@@ -428,7 +660,6 @@ Values are **average capture time (ms)** → lower is better.
|
|
|
428
660
|
| Large Scroll (2000×1500) | **0.5 ms** | 0.8 ms | 186.3 ms | 3.2 ms |
|
|
429
661
|
| Very Large (4000×2000) | **0.5 ms** | 0.9 ms | 425.9 ms | 3.3 ms |
|
|
430
662
|
|
|
431
|
-
---
|
|
432
663
|
|
|
433
664
|
### Complex elements
|
|
434
665
|
|
|
@@ -455,7 +686,7 @@ npm run test:benchmark
|
|
|
455
686
|
|
|
456
687
|
Planned improvements for future versions of SnapDOM:
|
|
457
688
|
|
|
458
|
-
* [
|
|
689
|
+
* [X] **Implement plugin system**
|
|
459
690
|
SnapDOM will support external plugins to extend or override internal behavior (e.g. custom node transformers, exporters, or filters).
|
|
460
691
|
|
|
461
692
|
* [ ] **Refactor to modular architecture**
|
|
@@ -470,7 +701,7 @@ Planned improvements for future versions of SnapDOM:
|
|
|
470
701
|
* [X] **Auto font preloading**
|
|
471
702
|
Required fonts will be automatically detected and preloaded before capture, reducing the need for manual `preCache()` calls.
|
|
472
703
|
|
|
473
|
-
* [
|
|
704
|
+
* [X] **Document plugin development**
|
|
474
705
|
A full guide will be provided for creating and registering custom SnapDOM plugins.
|
|
475
706
|
|
|
476
707
|
* [ ] **Make export utilities tree-shakeable**
|