@zumer/snapdom 0.9.9 → 1.0.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 +88 -94
- package/dist/snapdom.js +114 -83
- package/dist/snapdom.min.js +3 -3
- package/dist/snapdom.min.mjs +3 -3
- package/dist/snapdom.mjs +114 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,38 +1,31 @@
|
|
|
1
1
|
# snapDOM
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
<p align="center">
|
|
5
4
|
<a href="http://zumerlab.github.io/orbit-docs">
|
|
6
5
|
<img src="https://raw.githubusercontent.com/zumerlab/snapdom/main/docs/assets/hero.png" width="80%">
|
|
7
6
|
</a>
|
|
8
7
|
</p>
|
|
9
8
|
|
|
10
|
-
|
|
11
9
|
<p align="center">
|
|
12
10
|
<a href="https://www.npmjs.com/package/@zumer/snapdom"><img src="https://img.shields.io/github/package-json/v/zumerlab/snapdom"></a>
|
|
13
11
|
</p>
|
|
14
12
|
|
|
13
|
+
**snapDOM** is a fast and accurate DOM-to-image capture tool built for **Zumly**, a zoom-based view transition framework.
|
|
15
14
|
|
|
15
|
+
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.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- 📸 Full DOM capture
|
|
23
|
-
- 🎨 Embedded styles, pseudo-elements, and fonts
|
|
24
|
-
- 🖼️ Export to SVG, PNG, JPG, WebP, or `canvas`
|
|
25
|
-
- ⚡ Ultra fast, no dependencies
|
|
26
|
-
- 📦 100% based on standard Web APIs
|
|
17
|
+
* 📸 Full DOM capture
|
|
18
|
+
* 🎨 Embedded styles, pseudo-elements, and fonts
|
|
19
|
+
* 🖼️ Export to SVG, PNG, JPG, WebP, or `canvas`
|
|
20
|
+
* ⚡ Ultra fast, no dependencies
|
|
21
|
+
* 📦 100% based on standard Web APIs
|
|
27
22
|
|
|
28
23
|
## Demo
|
|
29
24
|
|
|
30
|
-
https://zumerlab.github.io/snapdom/
|
|
25
|
+
[https://zumerlab.github.io/snapdom/](https://zumerlab.github.io/snapdom/)
|
|
31
26
|
|
|
32
27
|
## Installation
|
|
33
28
|
|
|
34
|
-
You can use **snapDOM** via **NPM**, **CDN**, **script tag**, or by **importing as a module**.
|
|
35
|
-
|
|
36
29
|
### NPM / Yarn
|
|
37
30
|
|
|
38
31
|
```sh
|
|
@@ -55,15 +48,13 @@ yarn add @zumer/snapdom
|
|
|
55
48
|
<script src="snapdom.js"></script>
|
|
56
49
|
```
|
|
57
50
|
|
|
58
|
-
The global object `snapdom` will be available.
|
|
59
|
-
|
|
60
51
|
### ES Module
|
|
61
52
|
|
|
62
|
-
```
|
|
53
|
+
```js
|
|
63
54
|
import { snapdom } from './snapdom.mjs';
|
|
64
55
|
```
|
|
65
56
|
|
|
66
|
-
###
|
|
57
|
+
### Module via CDN
|
|
67
58
|
|
|
68
59
|
```html
|
|
69
60
|
<script type="module">
|
|
@@ -71,70 +62,101 @@ import { snapdom } from './snapdom.mjs';
|
|
|
71
62
|
</script>
|
|
72
63
|
```
|
|
73
64
|
|
|
74
|
-
Now you can call `snapdom(el)`, `snapdom.toPng(el)`, etc., directly in your JavaScript.
|
|
75
|
-
|
|
76
65
|
## Basic usage
|
|
77
66
|
|
|
78
|
-
|
|
79
|
-
// Capture an element as <img> tag
|
|
80
|
-
const el = document.querySelector("#myElement")
|
|
81
|
-
const img = await snapdom.toImg(el);
|
|
67
|
+
### Reusable result (recommended)
|
|
82
68
|
|
|
69
|
+
```js
|
|
70
|
+
const el = document.querySelector('#target');
|
|
71
|
+
const result = await snapdom(el, { scale: 2 });
|
|
72
|
+
|
|
73
|
+
const img = await result.toPng();
|
|
83
74
|
document.body.appendChild(img);
|
|
75
|
+
|
|
76
|
+
await result.download({ format: 'jpg', filename: 'my-capture' });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### One-step shortcuts
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const el = document.querySelector('#target');
|
|
83
|
+
const png = await snapdom.toPng(el);
|
|
84
|
+
document.body.appendChild(png);
|
|
85
|
+
|
|
86
|
+
const blob = await snapdom.toBlob(el);
|
|
84
87
|
```
|
|
85
88
|
|
|
86
89
|
## API
|
|
87
90
|
|
|
88
|
-
###
|
|
91
|
+
### `snapdom(el, options?)`
|
|
92
|
+
|
|
93
|
+
Returns an object with reusable export methods:
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
```js
|
|
96
|
+
{
|
|
97
|
+
url: string;
|
|
98
|
+
toRaw(): string;
|
|
99
|
+
toImg(): Promise<HTMLImageElement>;
|
|
100
|
+
toCanvas(): Promise<HTMLCanvasElement>;
|
|
101
|
+
toBlob(): Promise<Blob>;
|
|
102
|
+
toPng(): Promise<HTMLImageElement>;
|
|
103
|
+
toJpg(options?): Promise<HTMLImageElement>;
|
|
104
|
+
toWebp(options?): Promise<HTMLImageElement>;
|
|
105
|
+
download(options?): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Shortcut methods
|
|
91
110
|
|
|
92
|
-
| Method
|
|
93
|
-
|
|
|
94
|
-
| `snapdom(el, options?)`
|
|
95
|
-
| `snapdom.
|
|
96
|
-
| `snapdom.
|
|
97
|
-
| `snapdom.toPng(el, options?)`
|
|
98
|
-
| `snapdom.toJpg(el, options?)`
|
|
99
|
-
| `snapdom.toWebp(el, options?)`
|
|
100
|
-
| `snapdom.
|
|
101
|
-
| `preCache(root?, options?)` | Preload resources for faster and more accurate captures |
|
|
111
|
+
| Method | Description |
|
|
112
|
+
| ------------------------------ | ------------------------------------- |
|
|
113
|
+
| `snapdom.toImg(el, options?)` | Returns an `HTMLImageElement` |
|
|
114
|
+
| `snapdom.toCanvas(el, options?) ` | Returns a `Canvas` |
|
|
115
|
+
| `snapdom.toBlob(el, options?)` | Returns an SVG `Blob` |
|
|
116
|
+
| `snapdom.toPng(el, options?)` | Returns a PNG image |
|
|
117
|
+
| `snapdom.toJpg(el, options?)` | Returns a JPG image |
|
|
118
|
+
| `snapdom.toWebp(el, options?)` | Returns a WebP image |
|
|
119
|
+
| `snapdom.download(el, options?) ` | Triggers download in specified format |
|
|
102
120
|
|
|
103
|
-
|
|
121
|
+
## Options
|
|
104
122
|
|
|
105
|
-
|
|
123
|
+
All capture methods accept an `options` object:
|
|
106
124
|
|
|
107
|
-
|
|
125
|
+
| Option | Type | Default | Description |
|
|
126
|
+
| ----------------- | ------- | -------- | ------------------------------------------ |
|
|
127
|
+
| `compress` | boolean | `true` | Removes redundant styles |
|
|
128
|
+
| `fast` | boolean | `true` | Skips idle delay for faster results |
|
|
129
|
+
| `embedFonts` | boolean | `false` | Inlines fonts (icon fonts always embedded) |
|
|
130
|
+
| `scale` | number | `1` | Output scale multiplier |
|
|
131
|
+
| `backgroundColor` | string | `"#fff"` | Fallback color for JPG/WebP |
|
|
132
|
+
| `quality` | number | `1` | Quality for JPG/WebP (0 to 1) |
|
|
108
133
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
### Download options
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
{
|
|
138
|
+
format?: "svg" | "png" | "jpg" | "jpeg" | "webp"; // default: "png"
|
|
139
|
+
filename?: string; // default: "capture"
|
|
140
|
+
backgroundColor?: string; // optional override
|
|
141
|
+
}
|
|
142
|
+
```
|
|
117
143
|
|
|
118
|
-
---
|
|
119
144
|
|
|
120
145
|
### `preCache()` – Optional helper
|
|
121
146
|
|
|
122
|
-
The `preCache()` function can be used to load external resources (like images and fonts) in advance.
|
|
147
|
+
The `preCache()` function can be used to load external resources (like images and fonts) in advance. It is specially useful when the element to capure is big and complex.
|
|
123
148
|
|
|
124
149
|
```js
|
|
125
150
|
import { preCache } from '@zumer/snapdom';
|
|
126
151
|
|
|
127
|
-
await preCache(document.body
|
|
128
|
-
embedFonts: true,
|
|
129
|
-
preWarm: true
|
|
130
|
-
});
|
|
152
|
+
await preCache(document.body);
|
|
131
153
|
```
|
|
132
154
|
|
|
133
155
|
```js
|
|
134
156
|
import { snapdom, preCache } from './snapdom.mjs';
|
|
135
157
|
window.addEventListener('load', async () => {
|
|
136
158
|
await preCache();
|
|
137
|
-
|
|
159
|
+
console.log('📦 Resources preloaded');
|
|
138
160
|
});
|
|
139
161
|
```
|
|
140
162
|
|
|
@@ -142,50 +164,22 @@ import { snapdom, preCache } from './snapdom.mjs';
|
|
|
142
164
|
|
|
143
165
|
* `embedFonts` *(boolean, default: true)* — Inlines non-icon fonts during preload.
|
|
144
166
|
* `reset` *(boolean, default: false)* — Clears all existing internal caches.
|
|
145
|
-
* `preWarm` *(boolean, default: true)* — Runs a fake capture in memory to warm up styles, fonts and layout detection.
|
|
146
|
-
|
|
147
|
-
## Special features
|
|
148
|
-
|
|
149
|
-
* **Shadow DOM**: Captures content inside Web Components and `shadowRoot`.
|
|
150
|
-
* **Pseudo-elements**: Captures `::before` and `::after`, including background images.
|
|
151
|
-
* **Backgrounds and images**: Inlines external images as Data URLs.
|
|
152
|
-
* **Fonts**: Replicates applied font families without requiring external font files.
|
|
153
|
-
* **Icon fonts**: Captures icon fonts like **Font Awesome** and **Material Icons**.
|
|
154
|
-
* **Placeholder and Exclusion**:
|
|
155
|
-
|
|
156
|
-
* `data-capture="exclude"`: Skips an element while preserving layout space.
|
|
157
|
-
* `data-capture="placeholder"` + `data-placeholder-text="Text"`: Replaces an element with placeholder text.
|
|
158
167
|
|
|
159
|
-
**Now with improved fidelity and even faster performance.**
|
|
160
168
|
|
|
161
|
-
##
|
|
169
|
+
## Features
|
|
162
170
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<button id="captureBtn">Capture as img</button>
|
|
170
|
-
|
|
171
|
-
<script type="module">
|
|
172
|
-
import { snapdom } from './snapdom.mjs';
|
|
173
|
-
|
|
174
|
-
const button = document.getElementById('captureBtn');
|
|
175
|
-
button.addEventListener('click', async () => {
|
|
176
|
-
const target = document.getElementById('captureMe');
|
|
177
|
-
const img = await snapdom.toJpg(target, { quality: 0.5});
|
|
178
|
-
document.body.appendChild(img);
|
|
179
|
-
});
|
|
180
|
-
</script>
|
|
181
|
-
```
|
|
171
|
+
* Captures **shadow DOM** and Web Components
|
|
172
|
+
* Supports `::before` and `::after` pseudo-elements
|
|
173
|
+
* Inlines background images and fonts
|
|
174
|
+
* Handles **Font Awesome**, **Material Icons**, and more
|
|
175
|
+
* `data-capture="exclude"` to ignore an element
|
|
176
|
+
* `data-capture="placeholder"` with `data-placeholder-text` for masked replacements
|
|
182
177
|
|
|
183
178
|
## Limitations
|
|
184
179
|
|
|
185
|
-
* External images must be CORS-accessible
|
|
186
|
-
* Iframes are not
|
|
187
|
-
|
|
188
|
-
### Please if you find a bug **open an issue**.
|
|
180
|
+
* External images must be CORS-accessible
|
|
181
|
+
* Iframes are not supported
|
|
182
|
+
* When WebP format is used on Safari, it will fallback to PNG rendering.
|
|
189
183
|
|
|
190
184
|
## Benchmarks
|
|
191
185
|
|
|
@@ -221,7 +215,7 @@ npm run test:benchmark
|
|
|
221
215
|
|
|
222
216
|
They execute in **headless Chromium** using real DOM nodes.
|
|
223
217
|
|
|
218
|
+
|
|
224
219
|
## License
|
|
225
220
|
|
|
226
221
|
MIT © Zumerlab
|
|
227
|
-
|
package/dist/snapdom.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.0.
|
|
4
|
+
* v.1.0.0
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -247,6 +247,9 @@
|
|
|
247
247
|
}
|
|
248
248
|
return snap;
|
|
249
249
|
}
|
|
250
|
+
function isSafari() {
|
|
251
|
+
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
252
|
+
}
|
|
250
253
|
|
|
251
254
|
// src/modules/styles.js
|
|
252
255
|
function inlineAllStyles(source, clone, styleMap, cache, compress) {
|
|
@@ -291,6 +294,9 @@
|
|
|
291
294
|
img.src = dataURL;
|
|
292
295
|
img.width = node.width;
|
|
293
296
|
img.height = node.height;
|
|
297
|
+
img.style.display = "inline-block";
|
|
298
|
+
img.style.width = `${node.width}px`;
|
|
299
|
+
img.style.height = `${node.height}px`;
|
|
294
300
|
return img;
|
|
295
301
|
}
|
|
296
302
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
@@ -596,14 +602,12 @@
|
|
|
596
602
|
await new Promise((resolve) => {
|
|
597
603
|
idle(() => {
|
|
598
604
|
const rect = element.getBoundingClientRect();
|
|
599
|
-
const w = rect.width
|
|
600
|
-
const h = rect.height
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
clone.style.
|
|
605
|
-
clone.style.width = `${rect.width}px`;
|
|
606
|
-
clone.style.height = `${rect.height}px`;
|
|
605
|
+
const w = Math.ceil(rect.width);
|
|
606
|
+
const h = Math.ceil(rect.height);
|
|
607
|
+
clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
608
|
+
clone.style.transformOrigin = "top left";
|
|
609
|
+
if (scale !== 1 && isSafari()) {
|
|
610
|
+
clone.style.scale = `${scale}`;
|
|
607
611
|
}
|
|
608
612
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
609
613
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
@@ -628,68 +632,122 @@
|
|
|
628
632
|
}
|
|
629
633
|
|
|
630
634
|
// src/api/snapdom.js
|
|
631
|
-
async function
|
|
632
|
-
|
|
633
|
-
|
|
635
|
+
async function toImg(url, { dpr = 1, scale = 1 }) {
|
|
636
|
+
const img = new Image();
|
|
637
|
+
img.src = url;
|
|
638
|
+
await img.decode();
|
|
639
|
+
if (isSafari) {
|
|
640
|
+
img.width = img.width * scale;
|
|
641
|
+
img.height = img.height * scale;
|
|
642
|
+
} else {
|
|
643
|
+
img.width = img.width / scale;
|
|
644
|
+
img.height = img.height / scale;
|
|
634
645
|
}
|
|
635
|
-
return
|
|
646
|
+
return img;
|
|
636
647
|
}
|
|
637
|
-
|
|
638
|
-
const url = await snapdom(el, options);
|
|
648
|
+
async function toCanvas(url, { dpr = 1, scale = 1 }) {
|
|
639
649
|
const img = new Image();
|
|
640
650
|
img.src = url;
|
|
641
651
|
await img.decode();
|
|
642
|
-
return img;
|
|
643
|
-
};
|
|
644
|
-
snapdom.toCanvas = async (el, options = {}) => {
|
|
645
|
-
const img = await snapdom.toImg(el, options);
|
|
646
652
|
const canvas = document.createElement("canvas");
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
canvas.
|
|
653
|
+
const width = img.width * scale * dpr;
|
|
654
|
+
const height = img.height * scale * dpr;
|
|
655
|
+
canvas.width = Math.ceil(width);
|
|
656
|
+
canvas.height = Math.ceil(height);
|
|
657
|
+
const ctx = canvas.getContext("2d");
|
|
658
|
+
ctx.scale(scale * dpr, scale * dpr);
|
|
659
|
+
ctx.drawImage(img, 0, 0);
|
|
650
660
|
return canvas;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
snapdom.toJpg = async (el, options = {}) => {
|
|
660
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
661
|
-
const canvas = await snapdom.toCanvas(el, options);
|
|
662
|
-
const temp = document.createElement("canvas");
|
|
663
|
-
temp.width = canvas.width;
|
|
664
|
-
temp.height = canvas.height;
|
|
665
|
-
const ctx = temp.getContext("2d");
|
|
666
|
-
ctx.fillStyle = backgroundColor;
|
|
667
|
-
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
668
|
-
ctx.drawImage(canvas, 0, 0);
|
|
669
|
-
const img = new Image();
|
|
670
|
-
img.src = temp.toDataURL("image/jpeg", quality);
|
|
671
|
-
await img.decode();
|
|
672
|
-
return img;
|
|
673
|
-
};
|
|
674
|
-
snapdom.toWebp = async (el, options = {}) => {
|
|
675
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
676
|
-
const canvas = await snapdom.toCanvas(el, options);
|
|
661
|
+
}
|
|
662
|
+
async function toBlob(url) {
|
|
663
|
+
const svgText = decodeURIComponent(url.split(",")[1]);
|
|
664
|
+
return new Blob([svgText], { type: "image/svg+xml" });
|
|
665
|
+
}
|
|
666
|
+
async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
|
|
667
|
+
const baseCanvas = await toCanvas(url, { dpr, scale });
|
|
668
|
+
if (!backgroundColor) return baseCanvas;
|
|
677
669
|
const temp = document.createElement("canvas");
|
|
678
|
-
temp.width =
|
|
679
|
-
temp.height =
|
|
670
|
+
temp.width = baseCanvas.width;
|
|
671
|
+
temp.height = baseCanvas.height;
|
|
680
672
|
const ctx = temp.getContext("2d");
|
|
681
673
|
ctx.fillStyle = backgroundColor;
|
|
682
674
|
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
683
|
-
ctx.drawImage(
|
|
675
|
+
ctx.drawImage(baseCanvas, 0, 0);
|
|
676
|
+
return temp;
|
|
677
|
+
}
|
|
678
|
+
async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
|
|
679
|
+
const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
|
|
684
680
|
const img = new Image();
|
|
685
|
-
img.src =
|
|
681
|
+
img.src = canvas.toDataURL(`image/${format}`, quality);
|
|
686
682
|
await img.decode();
|
|
683
|
+
img.style.width = `${canvas.width / dpr}px`;
|
|
684
|
+
img.style.height = `${canvas.height / dpr}px`;
|
|
687
685
|
return img;
|
|
686
|
+
}
|
|
687
|
+
async function download(url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
|
|
688
|
+
if (format === "svg") {
|
|
689
|
+
const blob = await toBlob(url);
|
|
690
|
+
const objectURL = URL.createObjectURL(blob);
|
|
691
|
+
const a2 = document.createElement("a");
|
|
692
|
+
a2.href = objectURL;
|
|
693
|
+
a2.download = `${filename}.svg`;
|
|
694
|
+
a2.click();
|
|
695
|
+
URL.revokeObjectURL(objectURL);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
|
|
699
|
+
const finalBg = backgroundColor ?? defaultBg;
|
|
700
|
+
const canvas = await createBackground(url, { dpr, scale }, finalBg);
|
|
701
|
+
const mime = {
|
|
702
|
+
jpg: "image/jpeg",
|
|
703
|
+
jpeg: "image/jpeg",
|
|
704
|
+
png: "image/png",
|
|
705
|
+
webp: "image/webp"
|
|
706
|
+
}[format] || "image/png";
|
|
707
|
+
const dataURL = canvas.toDataURL(mime);
|
|
708
|
+
const a = document.createElement("a");
|
|
709
|
+
a.href = dataURL;
|
|
710
|
+
a.download = `${filename}.${format}`;
|
|
711
|
+
a.click();
|
|
712
|
+
}
|
|
713
|
+
async function snapdom(element, options = {}) {
|
|
714
|
+
options = { scale: 1, ...options };
|
|
715
|
+
if (!element) throw new Error("Element cannot be null or undefined");
|
|
716
|
+
return await snapdom.capture(element, options);
|
|
717
|
+
}
|
|
718
|
+
snapdom.capture = async (el, options = {}) => {
|
|
719
|
+
const url = await captureDOM(el, options);
|
|
720
|
+
const dpr = window.devicePixelRatio || 1;
|
|
721
|
+
const scale = options.scale || 1;
|
|
722
|
+
return {
|
|
723
|
+
url,
|
|
724
|
+
options,
|
|
725
|
+
toRaw: () => url,
|
|
726
|
+
toImg: () => toImg(url, { dpr, scale }),
|
|
727
|
+
toCanvas: () => toCanvas(url, { dpr, scale }),
|
|
728
|
+
toBlob: () => toBlob(url),
|
|
729
|
+
toPng: () => toRasterImg(url, { dpr, scale }, "png"),
|
|
730
|
+
toJpg: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "jpeg"),
|
|
731
|
+
toWebp: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "webp"),
|
|
732
|
+
download: ({ format = "png", filename = "capture", backgroundColor } = {}) => download(url, { dpr, scale, backgroundColor, format, filename })
|
|
733
|
+
};
|
|
688
734
|
};
|
|
689
|
-
snapdom.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
735
|
+
snapdom.toRaw = async (el, options) => (await snapdom.capture(el, options)).toRaw();
|
|
736
|
+
snapdom.toImg = async (el, options) => (await snapdom.capture(el, options)).toImg();
|
|
737
|
+
snapdom.toCanvas = async (el, options) => (await snapdom.capture(el, options)).toCanvas();
|
|
738
|
+
snapdom.toBlob = async (el, options) => (await snapdom.capture(el, options)).toBlob();
|
|
739
|
+
snapdom.toPng = async (el, options) => (await snapdom.capture(el, options)).toPng(options);
|
|
740
|
+
snapdom.toJpg = async (el, options) => (await snapdom.capture(el, options)).toJpg(options);
|
|
741
|
+
snapdom.toWebp = async (el, options) => (await snapdom.capture(el, options)).toWebp(options);
|
|
742
|
+
snapdom.download = async (el, options = {}) => {
|
|
743
|
+
const {
|
|
744
|
+
format = "png",
|
|
745
|
+
filename = "capture",
|
|
746
|
+
backgroundColor,
|
|
747
|
+
...rest
|
|
748
|
+
} = options;
|
|
749
|
+
const capture = await snapdom.capture(el, rest);
|
|
750
|
+
return await capture.download({ format, filename, backgroundColor });
|
|
693
751
|
};
|
|
694
752
|
|
|
695
753
|
// src/api/preCache.js
|
|
@@ -734,32 +792,5 @@
|
|
|
734
792
|
await embedCustomFonts({ ignoreIconFonts: true, preCached: true });
|
|
735
793
|
}
|
|
736
794
|
await Promise.all(promises);
|
|
737
|
-
if (preWarm) {
|
|
738
|
-
const usedTags = collectUsedTagNames(root).sort();
|
|
739
|
-
const tagKey = usedTags.join(",");
|
|
740
|
-
if (!baseCSSCache.has(tagKey)) {
|
|
741
|
-
const css = generateDedupedBaseCSS(usedTags);
|
|
742
|
-
baseCSSCache.set(tagKey, css);
|
|
743
|
-
}
|
|
744
|
-
const clone = root.cloneNode(false);
|
|
745
|
-
await inlinePseudoElements(
|
|
746
|
-
root,
|
|
747
|
-
clone,
|
|
748
|
-
/* @__PURE__ */ new Map(),
|
|
749
|
-
/* @__PURE__ */ new WeakMap(),
|
|
750
|
-
/* compress */
|
|
751
|
-
false
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
if (preWarm) {
|
|
755
|
-
try {
|
|
756
|
-
await prepareClone(
|
|
757
|
-
root,
|
|
758
|
-
/* compress: */
|
|
759
|
-
options.preWarm
|
|
760
|
-
);
|
|
761
|
-
} catch (e) {
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
795
|
}
|
|
765
796
|
})();
|
package/dist/snapdom.min.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
(()=>{var S=new Map,E=new Map
|
|
2
|
-
`;return o}function
|
|
3
|
-
`}catch{console.warn("\u274C No pude fetch CSS",
|
|
1
|
+
(()=>{var S=new Map,E=new Map,$=new Map,k=new Map,T=new Map,R=new WeakMap;var rt=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function _(){for(let t of rt)z(t)}function z(t){if(k.has(t))return k.get(t);if(new Set(["script","style","meta","link","noscript","template"]).has(t)){let s={};return k.set(t,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let n=document.createElement(t);n.style.all="initial",o.appendChild(n);let a=getComputedStyle(n),r={};for(let s of a)r[s]=a.getPropertyValue(s);return o.removeChild(n),k.set(t,r),r}function U(t,e,o=!1){let n=[],a=z(e);for(let[r,s]of Object.entries(t))if(!o)s&&n.push(`${r}:${s}`);else{let c=a[r];s&&s!==c&&n.push(`${r}:${s}`)}return n.sort().join(";")}function H(t){let e=new Set;return t.nodeType!==Node.ELEMENT_NODE&&t.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(t.tagName&&e.add(t.tagName.toLowerCase()),typeof t.querySelectorAll=="function"&&t.querySelectorAll("*").forEach(o=>e.add(o.tagName.toLowerCase())),Array.from(e))}function K(t){let e=new Map;for(let n of t){let a=k.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");e.has(r)||e.set(r,[]),e.get(r).push(n)}let o="";for(let[n,a]of e.entries())o+=`${a.join(",")} { ${n} }
|
|
2
|
+
`;return o}function J(t){let e=new Set(t.values()),o=new Map,n=1;for(let a of e)o.set(a,`c${n++}`);return o}function N(t,{fast:e=!1}={}){if(e)return t();"requestIdleCallback"in window?requestIdleCallback(t,{timeout:50}):setTimeout(t,1)}function A(t,e=null){if(!(t instanceof Element))return window.getComputedStyle(t,e);let o=R.get(t);if(o||(o=new Map,R.set(t,o)),!o.has(e)){let n=window.getComputedStyle(t,e);o.set(e,n)}return o.get(e)}function Y(t){let e=t.replace(/^['"]|['"]$/g,"");if(e.startsWith("\\"))try{return String.fromCharCode(parseInt(e.replace("\\",""),16))}catch{return e}return e}function X(t){let e=t.match(/url\(["']?([^"')]+)["']?\)/);return e?e[1]:null}function M(t){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(t))}function C(t,e=3e3){return S.has(t)?Promise.resolve(S.get(t)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},e),r=new Image;r.crossOrigin="anonymous",r.onload=async()=>{clearTimeout(a);try{await r.decode();let s=document.createElement("canvas");s.width=r.width,s.height=r.height,s.getContext("2d").drawImage(r,0,0);try{let i=s.toDataURL("image/png");S.set(t,i),o(i)}catch{n(new Error("CORS restrictions prevented image capture"))}}catch(s){n(s)}},r.onerror=s=>{clearTimeout(a),n(new Error("Failed to load image: "+(s.message||"Unknown error")))},r.src=t})}function B(t){let e={};for(let o of t)e[o]=t.getPropertyValue(o);return e}function F(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(t,e,o,n,a){if(t.tagName==="STYLE")return;n.has(t)||n.set(t,A(t));let r=n.get(t),s=B(r),c=t.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(e,i)}function D(t,e,o,n,a){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=t.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(t.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${t.offsetWidth}px; height: ${t.offsetHeight}px; background: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`,c}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let c=t.cloneNode(!1);n.set(c,t),O(t,c,e,o,a);let i=document.createElement("div");return i.textContent=t.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(t.tagName==="CANVAS"){let c=t.toDataURL(),i=document.createElement("img");return i.src=c,i.width=t.width,i.height=t.height,i.style.display="inline-block",i.style.width=`${t.width}px`,i.style.height=`${t.height}px`,i}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let i=t.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let r=t.cloneNode(!1);n.set(r,t),O(t,r,e,o,a);let s=document.createDocumentFragment();if(t.childNodes.forEach(c=>{let i=D(c,e,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),t.shadowRoot){let c=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>D(d,e,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(d=>i.appendChild(d)),r.appendChild(i)}return r}async function G(t,e,o,n=32,a="#000"){e=e.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${e}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${e}"`:` ${n}px "${e}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(t,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:t=!0,preCached:e=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(a=>a.href),n="";for(let a of o)try{let s=await(await fetch(a.href)).text();if(t&&(M(a.href)||M(s))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",a.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(s.matchAll(c)).map(async l=>{let m=l[1].replace(/["']/g,"");if(m.startsWith("http")||(m=new URL(m,a.href).href),t&&M(m))return console.log("\u23ED\uFE0F Ignorando icon font URL:",m),null;if($.has(m))return{original:l[0],inlined:`url(${$.get(m)})`};try{let h=await(await fetch(m)).blob(),f=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(m,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",m),null}})),d=s;for(let l of i)l&&(d=d.replace(l.original,l.inlined));n+=d+`
|
|
3
|
+
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&e){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(t,e,o,n,a){if(!(t instanceof Element)||!(e instanceof Element))return;for(let c of["::before","::after"])try{let i=A(t,c);if(!i)continue;let d=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),m=d&&d!=="none"&&d!=='""'&&d!=="''",g=l&&l.startsWith("url(");if(m||g){let h=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,u=parseInt(i.getPropertyValue("font-weight"))||!1,w=i.getPropertyValue("color")||"#000",y=document.createElement("span");y.dataset.snapdomPseudo=c;let L=B(i),v=U(L,"span",a);o.set(y,v);let P=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(d);if(P&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,f,w),b.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let I=document.createElement("img"),q=await C(b[1]);I.src=q,I.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(I)}catch(I){console.error(`[snapdom] Error in pseudo ${c} for`,t,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?e.insertBefore(y,e.firstChild):e.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,t,i)}let r=Array.from(t.children),s=Array.from(e.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(r.length,s.length);c++)await W(r[c],s[c],o,n,a)}async function Q(t,e=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=D(t,o,n,a,e)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(t,r,o,n,e)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,d])=>`.${d}{${i}}`).join("");for(let[i,d]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(d);l&&i.classList.add(l);let m=i.style?.backgroundImage;i.removeAttribute("style"),m&&m!=="none"&&(i.style.backgroundImage=m)}return{clone:r,classCSS:c,styleCache:n}}async function Z(t){let e=Array.from(t.querySelectorAll("img")),o=async n=>{let a=n.src;try{let r=await C(a);n.src=r,n.width||(n.width=n.naturalWidth||100),n.height||(n.height=n.naturalHeight||100)}catch{let r=document.createElement("div");r.style=`width: ${n.width||100}px; height: ${n.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${n.height||100}px; color: #666; font-size: 12px;`,r.innerText="img",n.replaceWith(r)}};for(let n=0;n<e.length;n+=4){let a=e.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function tt(t,e,o){let n=[[t,e]];for(;n.length;){let[a,r]=n.shift(),s=o.get(a)||A(a);o.has(a)||o.set(a,s);let c=s.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=l[1],g;E.has(m)?g=E.get(m):(g=await C(m),E.set(m,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),d=Array.from(r.children);for(let l=0;l<Math.min(i.length,d.length);l++)n.push([i[l],d[l]])}}async function et(t,e={}){if(!t)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=e,s,c,i,d="",l="",m,g;if({clone:s,classCSS:c,styleCache:i}=await Q(t,o),await new Promise(f=>{N(async()=>{await Z(s),f()},{fast:a})}),await new Promise(f=>{N(async()=>{await tt(t,s,i),f()},{fast:a})}),n&&await new Promise(f=>{N(async()=>{d=await j({ignoreIconFonts:!0}),f()},{fast:a})}),o){let f=H(s).sort(),u=f.join(",");T.has(u)?l=T.get(u):await new Promise(w=>{N(()=>{l=K(f),T.set(u,l),w()},{fast:a})})}await new Promise(f=>{N(()=>{let u=t.getBoundingClientRect(),w=Math.ceil(u.width),y=Math.ceil(u.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",r!==1&&F()&&(s.style.scale=`${r}`);let L="http://www.w3.org/2000/svg",v=document.createElementNS(L,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let P=document.createElement("style");P.textContent=l+d+"svg{overflow:visible;}"+c,v.appendChild(P),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${L}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",m=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),m}async function st(t,{dpr:e=1,scale:o=1}){let n=new Image;return n.src=t,await n.decode(),F?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function nt(t,{dpr:e=1,scale:o=1}){let n=new Image;n.src=t,await n.decode();let a=document.createElement("canvas"),r=n.width*o*e,s=n.height*o*e;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*e,o*e),c.drawImage(n,0,0),a}async function ot(t){let e=decodeURIComponent(t.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})}async function at(t,{dpr:e=1,scale:o=1},n){let a=await nt(t,{dpr:e,scale:o});if(!n)return a;let r=document.createElement("canvas");r.width=a.width,r.height=a.height;let s=r.getContext("2d");return s.fillStyle=n,s.fillRect(0,0,r.width,r.height),s.drawImage(a,0,0),r}async function V(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await at(t,{dpr:e,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/e}px`,c.style.height=`${s.height/e}px`,c}async function it(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await ot(t),h=URL.createObjectURL(g),f=document.createElement("a");f.href=h,f.download=`${r}.svg`,f.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await at(t,{dpr:e,scale:o},n??s),d={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(d),m=document.createElement("a");m.href=l,m.download=`${r}.${a}`,m.click()}async function p(t,e={}){if(e={scale:1,...e},!t)throw new Error("Element cannot be null or undefined");return await p.capture(t,e)}p.capture=async(t,e={})=>{let o=await et(t,e),n=window.devicePixelRatio||1,a=e.scale||1;return{url:o,options:e,toRaw:()=>o,toImg:()=>st(o,{dpr:n,scale:a}),toCanvas:()=>nt(o,{dpr:n,scale:a}),toBlob:()=>ot(o),toPng:()=>V(o,{dpr:n,scale:a},"png"),toJpg:r=>V(o,{dpr:n,scale:a,...r},"jpeg"),toWebp:r=>V(o,{dpr:n,scale:a,...r},"webp"),download:({format:r="png",filename:s="capture",backgroundColor:c}={})=>it(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};p.toRaw=async(t,e)=>(await p.capture(t,e)).toRaw();p.toImg=async(t,e)=>(await p.capture(t,e)).toImg();p.toCanvas=async(t,e)=>(await p.capture(t,e)).toCanvas();p.toBlob=async(t,e)=>(await p.capture(t,e)).toBlob();p.toPng=async(t,e)=>(await p.capture(t,e)).toPng(e);p.toJpg=async(t,e)=>(await p.capture(t,e)).toJpg(e);p.toWebp=async(t,e)=>(await p.capture(t,e)).toWebp(e);p.download=async(t,e={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=e;return await(await p.capture(t,r)).download({format:o,filename:n,backgroundColor:a})};async function ct(t=document,e={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=e;if(n){S.clear(),E.clear(),$.clear(),T.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];t?.querySelectorAll&&(r=Array.from(t.querySelectorAll("img[src]")),s=Array.from(t.querySelectorAll("*")));let c=[];for(let i of r){let d=i.src;S.has(d)||c.push(C(d).then(l=>S.set(d,l)).catch(()=>{}))}for(let i of s){let d=getComputedStyle(i).backgroundImage,l=X(d);l&&!E.has(l)&&c.push(C(l).then(m=>E.set(l,m)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}})();
|
package/dist/snapdom.min.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var S=new Map,E=new Map
|
|
2
|
-
`;return o}function
|
|
3
|
-
`}catch{console.warn("\u274C No pude fetch CSS",
|
|
1
|
+
var S=new Map,E=new Map,$=new Map,k=new Map,T=new Map,R=new WeakMap;var rt=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function _(){for(let t of rt)z(t)}function z(t){if(k.has(t))return k.get(t);if(new Set(["script","style","meta","link","noscript","template"]).has(t)){let s={};return k.set(t,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let n=document.createElement(t);n.style.all="initial",o.appendChild(n);let a=getComputedStyle(n),r={};for(let s of a)r[s]=a.getPropertyValue(s);return o.removeChild(n),k.set(t,r),r}function U(t,e,o=!1){let n=[],a=z(e);for(let[r,s]of Object.entries(t))if(!o)s&&n.push(`${r}:${s}`);else{let c=a[r];s&&s!==c&&n.push(`${r}:${s}`)}return n.sort().join(";")}function H(t){let e=new Set;return t.nodeType!==Node.ELEMENT_NODE&&t.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(t.tagName&&e.add(t.tagName.toLowerCase()),typeof t.querySelectorAll=="function"&&t.querySelectorAll("*").forEach(o=>e.add(o.tagName.toLowerCase())),Array.from(e))}function K(t){let e=new Map;for(let n of t){let a=k.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");e.has(r)||e.set(r,[]),e.get(r).push(n)}let o="";for(let[n,a]of e.entries())o+=`${a.join(",")} { ${n} }
|
|
2
|
+
`;return o}function J(t){let e=new Set(t.values()),o=new Map,n=1;for(let a of e)o.set(a,`c${n++}`);return o}function N(t,{fast:e=!1}={}){if(e)return t();"requestIdleCallback"in window?requestIdleCallback(t,{timeout:50}):setTimeout(t,1)}function A(t,e=null){if(!(t instanceof Element))return window.getComputedStyle(t,e);let o=R.get(t);if(o||(o=new Map,R.set(t,o)),!o.has(e)){let n=window.getComputedStyle(t,e);o.set(e,n)}return o.get(e)}function Y(t){let e=t.replace(/^['"]|['"]$/g,"");if(e.startsWith("\\"))try{return String.fromCharCode(parseInt(e.replace("\\",""),16))}catch{return e}return e}function X(t){let e=t.match(/url\(["']?([^"')]+)["']?\)/);return e?e[1]:null}function M(t){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(t))}function C(t,e=3e3){return S.has(t)?Promise.resolve(S.get(t)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},e),r=new Image;r.crossOrigin="anonymous",r.onload=async()=>{clearTimeout(a);try{await r.decode();let s=document.createElement("canvas");s.width=r.width,s.height=r.height,s.getContext("2d").drawImage(r,0,0);try{let i=s.toDataURL("image/png");S.set(t,i),o(i)}catch{n(new Error("CORS restrictions prevented image capture"))}}catch(s){n(s)}},r.onerror=s=>{clearTimeout(a),n(new Error("Failed to load image: "+(s.message||"Unknown error")))},r.src=t})}function B(t){let e={};for(let o of t)e[o]=t.getPropertyValue(o);return e}function F(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(t,e,o,n,a){if(t.tagName==="STYLE")return;n.has(t)||n.set(t,A(t));let r=n.get(t),s=B(r),c=t.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(e,i)}function D(t,e,o,n,a){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=t.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(t.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${t.offsetWidth}px; height: ${t.offsetHeight}px; background: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`,c}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let c=t.cloneNode(!1);n.set(c,t),O(t,c,e,o,a);let i=document.createElement("div");return i.textContent=t.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(t.tagName==="CANVAS"){let c=t.toDataURL(),i=document.createElement("img");return i.src=c,i.width=t.width,i.height=t.height,i.style.display="inline-block",i.style.width=`${t.width}px`,i.style.height=`${t.height}px`,i}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let i=t.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let r=t.cloneNode(!1);n.set(r,t),O(t,r,e,o,a);let s=document.createDocumentFragment();if(t.childNodes.forEach(c=>{let i=D(c,e,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),t.shadowRoot){let c=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>D(d,e,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(d=>i.appendChild(d)),r.appendChild(i)}return r}async function G(t,e,o,n=32,a="#000"){e=e.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${e}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${e}"`:` ${n}px "${e}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(t,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:t=!0,preCached:e=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(a=>a.href),n="";for(let a of o)try{let s=await(await fetch(a.href)).text();if(t&&(M(a.href)||M(s))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",a.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(s.matchAll(c)).map(async l=>{let m=l[1].replace(/["']/g,"");if(m.startsWith("http")||(m=new URL(m,a.href).href),t&&M(m))return console.log("\u23ED\uFE0F Ignorando icon font URL:",m),null;if($.has(m))return{original:l[0],inlined:`url(${$.get(m)})`};try{let h=await(await fetch(m)).blob(),f=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(m,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",m),null}})),d=s;for(let l of i)l&&(d=d.replace(l.original,l.inlined));n+=d+`
|
|
3
|
+
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&e){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(t,e,o,n,a){if(!(t instanceof Element)||!(e instanceof Element))return;for(let c of["::before","::after"])try{let i=A(t,c);if(!i)continue;let d=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),m=d&&d!=="none"&&d!=='""'&&d!=="''",g=l&&l.startsWith("url(");if(m||g){let h=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,u=parseInt(i.getPropertyValue("font-weight"))||!1,w=i.getPropertyValue("color")||"#000",y=document.createElement("span");y.dataset.snapdomPseudo=c;let L=B(i),v=U(L,"span",a);o.set(y,v);let P=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(d);if(P&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,f,w),b.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let I=document.createElement("img"),q=await C(b[1]);I.src=q,I.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(I)}catch(I){console.error(`[snapdom] Error in pseudo ${c} for`,t,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?e.insertBefore(y,e.firstChild):e.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,t,i)}let r=Array.from(t.children),s=Array.from(e.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(r.length,s.length);c++)await W(r[c],s[c],o,n,a)}async function Q(t,e=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=D(t,o,n,a,e)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(t,r,o,n,e)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,d])=>`.${d}{${i}}`).join("");for(let[i,d]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(d);l&&i.classList.add(l);let m=i.style?.backgroundImage;i.removeAttribute("style"),m&&m!=="none"&&(i.style.backgroundImage=m)}return{clone:r,classCSS:c,styleCache:n}}async function Z(t){let e=Array.from(t.querySelectorAll("img")),o=async n=>{let a=n.src;try{let r=await C(a);n.src=r,n.width||(n.width=n.naturalWidth||100),n.height||(n.height=n.naturalHeight||100)}catch{let r=document.createElement("div");r.style=`width: ${n.width||100}px; height: ${n.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${n.height||100}px; color: #666; font-size: 12px;`,r.innerText="img",n.replaceWith(r)}};for(let n=0;n<e.length;n+=4){let a=e.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function tt(t,e,o){let n=[[t,e]];for(;n.length;){let[a,r]=n.shift(),s=o.get(a)||A(a);o.has(a)||o.set(a,s);let c=s.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=l[1],g;E.has(m)?g=E.get(m):(g=await C(m),E.set(m,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),d=Array.from(r.children);for(let l=0;l<Math.min(i.length,d.length);l++)n.push([i[l],d[l]])}}async function et(t,e={}){if(!t)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=e,s,c,i,d="",l="",m,g;if({clone:s,classCSS:c,styleCache:i}=await Q(t,o),await new Promise(f=>{N(async()=>{await Z(s),f()},{fast:a})}),await new Promise(f=>{N(async()=>{await tt(t,s,i),f()},{fast:a})}),n&&await new Promise(f=>{N(async()=>{d=await j({ignoreIconFonts:!0}),f()},{fast:a})}),o){let f=H(s).sort(),u=f.join(",");T.has(u)?l=T.get(u):await new Promise(w=>{N(()=>{l=K(f),T.set(u,l),w()},{fast:a})})}await new Promise(f=>{N(()=>{let u=t.getBoundingClientRect(),w=Math.ceil(u.width),y=Math.ceil(u.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",r!==1&&F()&&(s.style.scale=`${r}`);let L="http://www.w3.org/2000/svg",v=document.createElementNS(L,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let P=document.createElement("style");P.textContent=l+d+"svg{overflow:visible;}"+c,v.appendChild(P),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${L}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",m=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),m}async function st(t,{dpr:e=1,scale:o=1}){let n=new Image;return n.src=t,await n.decode(),F?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function nt(t,{dpr:e=1,scale:o=1}){let n=new Image;n.src=t,await n.decode();let a=document.createElement("canvas"),r=n.width*o*e,s=n.height*o*e;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*e,o*e),c.drawImage(n,0,0),a}async function ot(t){let e=decodeURIComponent(t.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})}async function at(t,{dpr:e=1,scale:o=1},n){let a=await nt(t,{dpr:e,scale:o});if(!n)return a;let r=document.createElement("canvas");r.width=a.width,r.height=a.height;let s=r.getContext("2d");return s.fillStyle=n,s.fillRect(0,0,r.width,r.height),s.drawImage(a,0,0),r}async function V(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await at(t,{dpr:e,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/e}px`,c.style.height=`${s.height/e}px`,c}async function it(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await ot(t),h=URL.createObjectURL(g),f=document.createElement("a");f.href=h,f.download=`${r}.svg`,f.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await at(t,{dpr:e,scale:o},n??s),d={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(d),m=document.createElement("a");m.href=l,m.download=`${r}.${a}`,m.click()}async function p(t,e={}){if(e={scale:1,...e},!t)throw new Error("Element cannot be null or undefined");return await p.capture(t,e)}p.capture=async(t,e={})=>{let o=await et(t,e),n=window.devicePixelRatio||1,a=e.scale||1;return{url:o,options:e,toRaw:()=>o,toImg:()=>st(o,{dpr:n,scale:a}),toCanvas:()=>nt(o,{dpr:n,scale:a}),toBlob:()=>ot(o),toPng:()=>V(o,{dpr:n,scale:a},"png"),toJpg:r=>V(o,{dpr:n,scale:a,...r},"jpeg"),toWebp:r=>V(o,{dpr:n,scale:a,...r},"webp"),download:({format:r="png",filename:s="capture",backgroundColor:c}={})=>it(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};p.toRaw=async(t,e)=>(await p.capture(t,e)).toRaw();p.toImg=async(t,e)=>(await p.capture(t,e)).toImg();p.toCanvas=async(t,e)=>(await p.capture(t,e)).toCanvas();p.toBlob=async(t,e)=>(await p.capture(t,e)).toBlob();p.toPng=async(t,e)=>(await p.capture(t,e)).toPng(e);p.toJpg=async(t,e)=>(await p.capture(t,e)).toJpg(e);p.toWebp=async(t,e)=>(await p.capture(t,e)).toWebp(e);p.download=async(t,e={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=e;return await(await p.capture(t,r)).download({format:o,filename:n,backgroundColor:a})};async function ct(t=document,e={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=e;if(n){S.clear(),E.clear(),$.clear(),T.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];t?.querySelectorAll&&(r=Array.from(t.querySelectorAll("img[src]")),s=Array.from(t.querySelectorAll("*")));let c=[];for(let i of r){let d=i.src;S.has(d)||c.push(C(d).then(l=>S.set(d,l)).catch(()=>{}))}for(let i of s){let d=getComputedStyle(i).backgroundImage,l=X(d);l&&!E.has(l)&&c.push(C(l).then(m=>E.set(l,m)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}export{ct as preCache,p as snapdom};
|
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.0.
|
|
4
|
+
* v.1.0.0
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -247,6 +247,9 @@ function snapshotComputedStyle(style) {
|
|
|
247
247
|
}
|
|
248
248
|
return snap;
|
|
249
249
|
}
|
|
250
|
+
function isSafari() {
|
|
251
|
+
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
252
|
+
}
|
|
250
253
|
|
|
251
254
|
// src/modules/styles.js
|
|
252
255
|
function inlineAllStyles(source, clone, styleMap, cache, compress) {
|
|
@@ -291,6 +294,9 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress) {
|
|
|
291
294
|
img.src = dataURL;
|
|
292
295
|
img.width = node.width;
|
|
293
296
|
img.height = node.height;
|
|
297
|
+
img.style.display = "inline-block";
|
|
298
|
+
img.style.width = `${node.width}px`;
|
|
299
|
+
img.style.height = `${node.height}px`;
|
|
294
300
|
return img;
|
|
295
301
|
}
|
|
296
302
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
@@ -596,14 +602,12 @@ async function captureDOM(element, options = {}) {
|
|
|
596
602
|
await new Promise((resolve) => {
|
|
597
603
|
idle(() => {
|
|
598
604
|
const rect = element.getBoundingClientRect();
|
|
599
|
-
const w = rect.width
|
|
600
|
-
const h = rect.height
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
clone.style.
|
|
605
|
-
clone.style.width = `${rect.width}px`;
|
|
606
|
-
clone.style.height = `${rect.height}px`;
|
|
605
|
+
const w = Math.ceil(rect.width);
|
|
606
|
+
const h = Math.ceil(rect.height);
|
|
607
|
+
clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
608
|
+
clone.style.transformOrigin = "top left";
|
|
609
|
+
if (scale !== 1 && isSafari()) {
|
|
610
|
+
clone.style.scale = `${scale}`;
|
|
607
611
|
}
|
|
608
612
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
609
613
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
@@ -628,68 +632,122 @@ async function captureDOM(element, options = {}) {
|
|
|
628
632
|
}
|
|
629
633
|
|
|
630
634
|
// src/api/snapdom.js
|
|
631
|
-
async function
|
|
632
|
-
|
|
633
|
-
|
|
635
|
+
async function toImg(url, { dpr = 1, scale = 1 }) {
|
|
636
|
+
const img = new Image();
|
|
637
|
+
img.src = url;
|
|
638
|
+
await img.decode();
|
|
639
|
+
if (isSafari) {
|
|
640
|
+
img.width = img.width * scale;
|
|
641
|
+
img.height = img.height * scale;
|
|
642
|
+
} else {
|
|
643
|
+
img.width = img.width / scale;
|
|
644
|
+
img.height = img.height / scale;
|
|
634
645
|
}
|
|
635
|
-
return
|
|
646
|
+
return img;
|
|
636
647
|
}
|
|
637
|
-
|
|
638
|
-
const url = await snapdom(el, options);
|
|
648
|
+
async function toCanvas(url, { dpr = 1, scale = 1 }) {
|
|
639
649
|
const img = new Image();
|
|
640
650
|
img.src = url;
|
|
641
651
|
await img.decode();
|
|
642
|
-
return img;
|
|
643
|
-
};
|
|
644
|
-
snapdom.toCanvas = async (el, options = {}) => {
|
|
645
|
-
const img = await snapdom.toImg(el, options);
|
|
646
652
|
const canvas = document.createElement("canvas");
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
canvas.
|
|
653
|
+
const width = img.width * scale * dpr;
|
|
654
|
+
const height = img.height * scale * dpr;
|
|
655
|
+
canvas.width = Math.ceil(width);
|
|
656
|
+
canvas.height = Math.ceil(height);
|
|
657
|
+
const ctx = canvas.getContext("2d");
|
|
658
|
+
ctx.scale(scale * dpr, scale * dpr);
|
|
659
|
+
ctx.drawImage(img, 0, 0);
|
|
650
660
|
return canvas;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
snapdom.toJpg = async (el, options = {}) => {
|
|
660
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
661
|
-
const canvas = await snapdom.toCanvas(el, options);
|
|
662
|
-
const temp = document.createElement("canvas");
|
|
663
|
-
temp.width = canvas.width;
|
|
664
|
-
temp.height = canvas.height;
|
|
665
|
-
const ctx = temp.getContext("2d");
|
|
666
|
-
ctx.fillStyle = backgroundColor;
|
|
667
|
-
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
668
|
-
ctx.drawImage(canvas, 0, 0);
|
|
669
|
-
const img = new Image();
|
|
670
|
-
img.src = temp.toDataURL("image/jpeg", quality);
|
|
671
|
-
await img.decode();
|
|
672
|
-
return img;
|
|
673
|
-
};
|
|
674
|
-
snapdom.toWebp = async (el, options = {}) => {
|
|
675
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
676
|
-
const canvas = await snapdom.toCanvas(el, options);
|
|
661
|
+
}
|
|
662
|
+
async function toBlob(url) {
|
|
663
|
+
const svgText = decodeURIComponent(url.split(",")[1]);
|
|
664
|
+
return new Blob([svgText], { type: "image/svg+xml" });
|
|
665
|
+
}
|
|
666
|
+
async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
|
|
667
|
+
const baseCanvas = await toCanvas(url, { dpr, scale });
|
|
668
|
+
if (!backgroundColor) return baseCanvas;
|
|
677
669
|
const temp = document.createElement("canvas");
|
|
678
|
-
temp.width =
|
|
679
|
-
temp.height =
|
|
670
|
+
temp.width = baseCanvas.width;
|
|
671
|
+
temp.height = baseCanvas.height;
|
|
680
672
|
const ctx = temp.getContext("2d");
|
|
681
673
|
ctx.fillStyle = backgroundColor;
|
|
682
674
|
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
683
|
-
ctx.drawImage(
|
|
675
|
+
ctx.drawImage(baseCanvas, 0, 0);
|
|
676
|
+
return temp;
|
|
677
|
+
}
|
|
678
|
+
async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
|
|
679
|
+
const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
|
|
684
680
|
const img = new Image();
|
|
685
|
-
img.src =
|
|
681
|
+
img.src = canvas.toDataURL(`image/${format}`, quality);
|
|
686
682
|
await img.decode();
|
|
683
|
+
img.style.width = `${canvas.width / dpr}px`;
|
|
684
|
+
img.style.height = `${canvas.height / dpr}px`;
|
|
687
685
|
return img;
|
|
686
|
+
}
|
|
687
|
+
async function download(url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
|
|
688
|
+
if (format === "svg") {
|
|
689
|
+
const blob = await toBlob(url);
|
|
690
|
+
const objectURL = URL.createObjectURL(blob);
|
|
691
|
+
const a2 = document.createElement("a");
|
|
692
|
+
a2.href = objectURL;
|
|
693
|
+
a2.download = `${filename}.svg`;
|
|
694
|
+
a2.click();
|
|
695
|
+
URL.revokeObjectURL(objectURL);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
|
|
699
|
+
const finalBg = backgroundColor ?? defaultBg;
|
|
700
|
+
const canvas = await createBackground(url, { dpr, scale }, finalBg);
|
|
701
|
+
const mime = {
|
|
702
|
+
jpg: "image/jpeg",
|
|
703
|
+
jpeg: "image/jpeg",
|
|
704
|
+
png: "image/png",
|
|
705
|
+
webp: "image/webp"
|
|
706
|
+
}[format] || "image/png";
|
|
707
|
+
const dataURL = canvas.toDataURL(mime);
|
|
708
|
+
const a = document.createElement("a");
|
|
709
|
+
a.href = dataURL;
|
|
710
|
+
a.download = `${filename}.${format}`;
|
|
711
|
+
a.click();
|
|
712
|
+
}
|
|
713
|
+
async function snapdom(element, options = {}) {
|
|
714
|
+
options = { scale: 1, ...options };
|
|
715
|
+
if (!element) throw new Error("Element cannot be null or undefined");
|
|
716
|
+
return await snapdom.capture(element, options);
|
|
717
|
+
}
|
|
718
|
+
snapdom.capture = async (el, options = {}) => {
|
|
719
|
+
const url = await captureDOM(el, options);
|
|
720
|
+
const dpr = window.devicePixelRatio || 1;
|
|
721
|
+
const scale = options.scale || 1;
|
|
722
|
+
return {
|
|
723
|
+
url,
|
|
724
|
+
options,
|
|
725
|
+
toRaw: () => url,
|
|
726
|
+
toImg: () => toImg(url, { dpr, scale }),
|
|
727
|
+
toCanvas: () => toCanvas(url, { dpr, scale }),
|
|
728
|
+
toBlob: () => toBlob(url),
|
|
729
|
+
toPng: () => toRasterImg(url, { dpr, scale }, "png"),
|
|
730
|
+
toJpg: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "jpeg"),
|
|
731
|
+
toWebp: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "webp"),
|
|
732
|
+
download: ({ format = "png", filename = "capture", backgroundColor } = {}) => download(url, { dpr, scale, backgroundColor, format, filename })
|
|
733
|
+
};
|
|
688
734
|
};
|
|
689
|
-
snapdom.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
735
|
+
snapdom.toRaw = async (el, options) => (await snapdom.capture(el, options)).toRaw();
|
|
736
|
+
snapdom.toImg = async (el, options) => (await snapdom.capture(el, options)).toImg();
|
|
737
|
+
snapdom.toCanvas = async (el, options) => (await snapdom.capture(el, options)).toCanvas();
|
|
738
|
+
snapdom.toBlob = async (el, options) => (await snapdom.capture(el, options)).toBlob();
|
|
739
|
+
snapdom.toPng = async (el, options) => (await snapdom.capture(el, options)).toPng(options);
|
|
740
|
+
snapdom.toJpg = async (el, options) => (await snapdom.capture(el, options)).toJpg(options);
|
|
741
|
+
snapdom.toWebp = async (el, options) => (await snapdom.capture(el, options)).toWebp(options);
|
|
742
|
+
snapdom.download = async (el, options = {}) => {
|
|
743
|
+
const {
|
|
744
|
+
format = "png",
|
|
745
|
+
filename = "capture",
|
|
746
|
+
backgroundColor,
|
|
747
|
+
...rest
|
|
748
|
+
} = options;
|
|
749
|
+
const capture = await snapdom.capture(el, rest);
|
|
750
|
+
return await capture.download({ format, filename, backgroundColor });
|
|
693
751
|
};
|
|
694
752
|
|
|
695
753
|
// src/api/preCache.js
|
|
@@ -734,33 +792,6 @@ async function preCache(root = document, options = {}) {
|
|
|
734
792
|
await embedCustomFonts({ ignoreIconFonts: true, preCached: true });
|
|
735
793
|
}
|
|
736
794
|
await Promise.all(promises);
|
|
737
|
-
if (preWarm) {
|
|
738
|
-
const usedTags = collectUsedTagNames(root).sort();
|
|
739
|
-
const tagKey = usedTags.join(",");
|
|
740
|
-
if (!baseCSSCache.has(tagKey)) {
|
|
741
|
-
const css = generateDedupedBaseCSS(usedTags);
|
|
742
|
-
baseCSSCache.set(tagKey, css);
|
|
743
|
-
}
|
|
744
|
-
const clone = root.cloneNode(false);
|
|
745
|
-
await inlinePseudoElements(
|
|
746
|
-
root,
|
|
747
|
-
clone,
|
|
748
|
-
/* @__PURE__ */ new Map(),
|
|
749
|
-
/* @__PURE__ */ new WeakMap(),
|
|
750
|
-
/* compress */
|
|
751
|
-
false
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
if (preWarm) {
|
|
755
|
-
try {
|
|
756
|
-
await prepareClone(
|
|
757
|
-
root,
|
|
758
|
-
/* compress: */
|
|
759
|
-
options.preWarm
|
|
760
|
-
);
|
|
761
|
-
} catch (e) {
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
795
|
}
|
|
765
796
|
export {
|
|
766
797
|
preCache,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zumer/snapdom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "snapDOM captures DOM elements as images with exceptional speed and minimal overhead. Designed to avoid bottlenecks and long tasks.",
|
|
5
5
|
"main": "dist/snapdom.js",
|
|
6
6
|
"module": "dist/snapdom.mjs",
|