@zumer/snapdom 0.9.9 → 1.0.0-pre.1747581859131
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 +77 -143
- 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,157 +62,100 @@ 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);
|
|
82
|
-
|
|
83
|
-
document.body.appendChild(img);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## API
|
|
87
|
-
|
|
88
|
-
### Note: API may evolve until v1.0.0!
|
|
89
|
-
|
|
90
|
-
The main API is exposed as `snapdom` and offers multiple capture methods:
|
|
91
|
-
|
|
92
|
-
| Method | Description |
|
|
93
|
-
| :------------------------------- | :------------------------------------------------------------- |
|
|
94
|
-
| `snapdom(el, options?)` | Captures as SVG Data URL |
|
|
95
|
-
| `snapdom.toImg(el, options?)` | Captures as `HTMLImageElement` (SVG) |
|
|
96
|
-
| `snapdom.toCanvas(el, options?)` | Captures as `HTMLCanvasElement` |
|
|
97
|
-
| `snapdom.toPng(el, options?)` | Captures as PNG image (`Image`) |
|
|
98
|
-
| `snapdom.toJpg(el, options?)` | Captures as JPG image (`Image`) |
|
|
99
|
-
| `snapdom.toWebp(el, options?)` | Captures as WebP image (`Image`) |
|
|
100
|
-
| `snapdom.toBlob(el, options?)` | Captures as SVG `Blob` |
|
|
101
|
-
| `preCache(root?, options?)` | Preload resources for faster and more accurate captures |
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
### Options
|
|
106
|
-
|
|
107
|
-
You can pass an `options` object to control the behavior of all capture methods:
|
|
108
|
-
|
|
109
|
-
| Option | Type | Default | Description |
|
|
110
|
-
| ----------------- | --------- | -------- | ----------------------------------------------------------------------------------- |
|
|
111
|
-
| `compress` | `boolean` | `true` | Removes default styles to reduce size and deduplicate CSS where possible. |
|
|
112
|
-
| `fast` | `boolean` | `true` | Skips `requestIdleCallback` delays and executes preparation immediately. |
|
|
113
|
-
| `embedFonts` | `boolean` | `false` | Inlines external fonts as Data URLs (except icon fonts, which are always embedded). |
|
|
114
|
-
| `scale` | `number` | `1` | Multiplies the output resolution without affecting layout size. |
|
|
115
|
-
| `backgroundColor` | `string` | `"#fff"` | Used when exporting to JPG or WebP formats (which don't support transparency). |
|
|
116
|
-
| `quality` | `number` | `1` | Compression quality for JPG/WebP, between `0` and `1`. |
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
### `preCache()` – Optional helper
|
|
121
|
-
|
|
122
|
-
The `preCache()` function can be used to load external resources (like images and fonts) in advance.
|
|
67
|
+
### Reusable result (recommended)
|
|
123
68
|
|
|
124
69
|
```js
|
|
125
|
-
|
|
70
|
+
const el = document.querySelector('#target');
|
|
71
|
+
const result = await snapdom(el, { scale: 2 });
|
|
126
72
|
|
|
127
|
-
await
|
|
128
|
-
|
|
129
|
-
preWarm: true
|
|
130
|
-
});
|
|
131
|
-
```
|
|
73
|
+
const img = await result.toPng();
|
|
74
|
+
document.body.appendChild(img);
|
|
132
75
|
|
|
133
|
-
|
|
134
|
-
import { snapdom, preCache } from './snapdom.mjs';
|
|
135
|
-
window.addEventListener('load', async () => {
|
|
136
|
-
await preCache();
|
|
137
|
-
console.log('📦 Resources preloaded');
|
|
138
|
-
});
|
|
76
|
+
await result.download({ format: 'jpg', filename: 'my-capture' });
|
|
139
77
|
```
|
|
140
78
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* `embedFonts` *(boolean, default: true)* — Inlines non-icon fonts during preload.
|
|
144
|
-
* `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
|
|
79
|
+
### One-step shortcuts
|
|
148
80
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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.
|
|
81
|
+
```js
|
|
82
|
+
const el = document.querySelector('#target');
|
|
83
|
+
const png = await snapdom.toPng(el);
|
|
84
|
+
document.body.appendChild(png);
|
|
158
85
|
|
|
159
|
-
|
|
86
|
+
const blob = await snapdom.toBlob(el);
|
|
87
|
+
```
|
|
160
88
|
|
|
161
|
-
##
|
|
89
|
+
## API
|
|
162
90
|
|
|
163
|
-
|
|
164
|
-
<div id="captureMe">
|
|
165
|
-
<h1 style="color: tomato;">Hello World!</h1>
|
|
166
|
-
<p>This content will be captured.</p>
|
|
167
|
-
</div>
|
|
91
|
+
### `snapdom(el, options?)`
|
|
168
92
|
|
|
169
|
-
|
|
93
|
+
Returns an object with reusable export methods:
|
|
170
94
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
}
|
|
181
107
|
```
|
|
182
108
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
* External images must be CORS-accessible.
|
|
186
|
-
* Iframes are not captured.
|
|
187
|
-
|
|
188
|
-
### Please if you find a bug **open an issue**.
|
|
109
|
+
### Shortcut methods
|
|
189
110
|
|
|
190
|
-
|
|
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 |
|
|
191
120
|
|
|
192
|
-
|
|
121
|
+
## Options
|
|
193
122
|
|
|
123
|
+
All capture methods accept an `options` object:
|
|
194
124
|
|
|
195
|
-
|
|
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) |
|
|
196
133
|
|
|
197
|
-
|
|
198
|
-
| -------------------------------- | :---------------------: | :---------------: |
|
|
199
|
-
| Small element (200×100) | 6.46× faster | 32.27× faster |
|
|
200
|
-
| Modal size (400×300) | 7.28× faster | 32.66× faster |
|
|
201
|
-
| Page view (1200×800) | 13.17× faster | 35.29× faster |
|
|
202
|
-
| Large scroll area (2000×1500) | 38.23× faster | 68.85× faster |
|
|
203
|
-
| Very large element (4000×2000) | 93.31× faster | 133.12× faster |
|
|
204
|
-
| Complex small element (200×100) | 3.97× faster | 15.23× faster |
|
|
205
|
-
| Complex modal (400×300) | 2.32× faster | 5.33× faster |
|
|
206
|
-
| Complex page (1200×800) | 1.62× faster | 1.65× faster |
|
|
207
|
-
| Complex large scroll (2000×1500) | 1.66× faster | 1.24× faster |
|
|
208
|
-
| Complex very large (4000×2000) | 1.52× faster | 1.28× faster |
|
|
134
|
+
### Download options
|
|
209
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
|
+
```
|
|
210
143
|
|
|
211
|
-
|
|
144
|
+
## Features
|
|
212
145
|
|
|
213
|
-
|
|
146
|
+
* Captures **shadow DOM** and Web Components
|
|
147
|
+
* Supports `::before` and `::after` pseudo-elements
|
|
148
|
+
* Inlines background images and fonts
|
|
149
|
+
* Handles **Font Awesome**, **Material Icons**, and more
|
|
150
|
+
* `data-capture="exclude"` to ignore an element
|
|
151
|
+
* `data-capture="placeholder"` with `data-placeholder-text` for masked replacements
|
|
214
152
|
|
|
215
|
-
|
|
216
|
-
git clone https://github.com/zumerlab/snapdom.git
|
|
217
|
-
cd snapdom
|
|
218
|
-
npm install
|
|
219
|
-
npm run test:benchmark
|
|
220
|
-
```
|
|
153
|
+
## Limitations
|
|
221
154
|
|
|
222
|
-
|
|
155
|
+
* External images must be CORS-accessible
|
|
156
|
+
* Iframes are not supported
|
|
157
|
+
* When WebP format is used on Safari, it will fallback to PNG rendering.
|
|
223
158
|
|
|
224
159
|
## License
|
|
225
160
|
|
|
226
161
|
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-pre.1747581859131
|
|
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,T=new Map,k=new Map,R=new WeakMap;var re=["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 e of re)z(e)}function z(e){if(T.has(e))return T.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let s={};return T.set(e,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(e);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),T.set(e,r),r}function U(e,t,o=!1){let n=[],a=z(t);for(let[r,s]of Object.entries(e))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(e){let t=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&t.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>t.add(o.tagName.toLowerCase())),Array.from(t))}function K(e){let t=new Map;for(let n of e){let a=T.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");t.has(r)||t.set(r,[]),t.get(r).push(n)}let o="";for(let[n,a]of t.entries())o+=`${a.join(",")} { ${n} }
|
|
2
|
+
`;return o}function J(e){let t=new Set(e.values()),o=new Map,n=1;for(let a of t)o.set(a,`c${n++}`);return o}function N(e,{fast:t=!1}={}){if(t)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function A(e,t=null){if(!(e instanceof Element))return window.getComputedStyle(e,t);let o=R.get(e);if(o||(o=new Map,R.set(e,o)),!o.has(t)){let n=window.getComputedStyle(e,t);o.set(t,n)}return o.get(t)}function Y(e){let t=e.replace(/^['"]|['"]$/g,"");if(t.startsWith("\\"))try{return String.fromCharCode(parseInt(t.replace("\\",""),16))}catch{return t}return t}function X(e){let t=e.match(/url\(["']?([^"')]+)["']?\)/);return t?t[1]:null}function B(e){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(e))}function C(e,t=3e3){return S.has(e)?Promise.resolve(S.get(e)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},t),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(e,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=e})}function M(e){let t={};for(let o of e)t[o]=e.getPropertyValue(o);return t}function D(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(e,t,o,n,a){if(e.tagName==="STYLE")return;n.has(e)||n.set(e,A(e));let r=n.get(e),s=M(r),c=e.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(t,i)}function F(e,t,o,n,a){if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=e.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(e.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${e.offsetWidth}px; height: ${e.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(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="placeholder"){let c=e.cloneNode(!1);n.set(c,e),O(e,c,t,o,a);let i=document.createElement("div");return i.textContent=e.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(e.tagName==="CANVAS"){let c=e.toDataURL(),i=document.createElement("img");return i.src=c,i.width=e.width,i.height=e.height,i.style.display="inline-block",i.style.width=`${e.width}px`,i.style.height=`${e.height}px`,i}if(e.nodeType===Node.TEXT_NODE){if(!e.textContent.trim())return null;if(e.parentElement?.shadowRoot){let i=e.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return e.cloneNode(!0)}if(e.nodeType!==Node.ELEMENT_NODE)return e.cloneNode(!0);let r=e.cloneNode(!1);n.set(r,e),O(e,r,t,o,a);let s=document.createDocumentFragment();if(e.childNodes.forEach(c=>{let i=F(c,t,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),e.shadowRoot){let c=Array.from(e.shadowRoot.children).filter(m=>m.tagName!=="STYLE").map(m=>F(m,t,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(m=>i.appendChild(m)),r.appendChild(i)}return r}async function G(e,t,o,n=32,a="#000"){t=t.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${t}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${t}"`:` ${n}px "${t}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(e,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:e=!0,preCached:t=!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(e&&(B(a.href)||B(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 d=l[1].replace(/["']/g,"");if(d.startsWith("http")||(d=new URL(d,a.href).href),e&&B(d))return console.log("\u23ED\uFE0F Ignorando icon font URL:",d),null;if($.has(d))return{original:l[0],inlined:`url(${$.get(d)})`};try{let h=await(await fetch(d)).blob(),p=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(d,p),{original:l[0],inlined:`url(${p})`}}catch{return console.warn("\u274C No pude fetch font",d),null}})),m=s;for(let l of i)l&&(m=m.replace(l.original,l.inlined));n+=m+`
|
|
3
|
+
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&t){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(e,t,o,n,a){if(!(e instanceof Element)||!(t instanceof Element))return;for(let c of["::before","::after"])try{let i=A(e,c);if(!i)continue;let m=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),d=m&&m!=="none"&&m!=='""'&&m!=="''",g=l&&l.startsWith("url(");if(d||g){let h=i.getPropertyValue("font-family"),p=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 P=M(i),v=U(P,"span",a);o.set(y,v);let L=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(m);if(L&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,p,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`,e,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?t.insertBefore(y,t.firstChild):t.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,e,i)}let r=Array.from(e.children),s=Array.from(t.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(e,t=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=F(e,o,n,a,t)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(e,r,o,n,t)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,m])=>`.${m}{${i}}`).join("");for(let[i,m]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(m);l&&i.classList.add(l);let d=i.style?.backgroundImage;i.removeAttribute("style"),d&&d!=="none"&&(i.style.backgroundImage=d)}return{clone:r,classCSS:c,styleCache:n}}async function Z(e){let t=Array.from(e.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<t.length;n+=4){let a=t.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function ee(e,t,o){let n=[[e,t]];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 d=l[1],g;E.has(d)?g=E.get(d):(g=await C(d),E.set(d,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),m=Array.from(r.children);for(let l=0;l<Math.min(i.length,m.length);l++)n.push([i[l],m[l]])}}async function te(e,t={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=t,s,c,i,m="",l="",d,g;if({clone:s,classCSS:c,styleCache:i}=await Q(e,o),await new Promise(p=>{N(async()=>{await Z(s),p()},{fast:a})}),await new Promise(p=>{N(async()=>{await ee(e,s,i),p()},{fast:a})}),n&&await new Promise(p=>{N(async()=>{m=await j({ignoreIconFonts:!0}),p()},{fast:a})}),o){let p=H(s).sort(),u=p.join(",");k.has(u)?l=k.get(u):await new Promise(w=>{N(()=>{l=K(p),k.set(u,l),w()},{fast:a})})}await new Promise(p=>{N(()=>{let u=e.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&&D()&&(s.style.scale=`${r}`);let P="http://www.w3.org/2000/svg",v=document.createElementNS(P,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let L=document.createElement("style");L.textContent=l+m+"svg{overflow:visible;}"+c,v.appendChild(L),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${P}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",d=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,p()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),d}async function se(e,{dpr:t=1,scale:o=1}){let n=new Image;return n.src=e,await n.decode(),D?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function ne(e,{dpr:t=1,scale:o=1}){let n=new Image;n.src=e,await n.decode();let a=document.createElement("canvas"),r=n.width*o*t,s=n.height*o*t;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*t,o*t),c.drawImage(n,0,0),a}async function oe(e){let t=decodeURIComponent(e.split(",")[1]);return new Blob([t],{type:"image/svg+xml"})}async function ae(e,{dpr:t=1,scale:o=1},n){let a=await ne(e,{dpr:t,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(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await ae(e,{dpr:t,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/t}px`,c.style.height=`${s.height/t}px`,c}async function ie(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await oe(e),h=URL.createObjectURL(g),p=document.createElement("a");p.href=h,p.download=`${r}.svg`,p.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await ae(e,{dpr:t,scale:o},n??s),m={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(m),d=document.createElement("a");d.href=l,d.download=`${r}.${a}`,d.click()}async function f(e,t={}){if(t={scale:1,...t},!e)throw new Error("Element cannot be null or undefined");return await f.capture(e,t)}f.capture=async(e,t={})=>{let o=await te(e,t),n=window.devicePixelRatio||1,a=t.scale||1;return{url:o,options:t,toRaw:()=>o,toImg:()=>se(o,{dpr:n,scale:a}),toCanvas:()=>ne(o,{dpr:n,scale:a}),toBlob:()=>oe(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}={})=>ie(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};f.toRaw=async(e,t)=>(await f.capture(e,t)).toRaw();f.toImg=async(e,t)=>(await f.capture(e,t)).toImg();f.toCanvas=async(e,t)=>(await f.capture(e,t)).toCanvas();f.toBlob=async(e,t)=>(await f.capture(e,t)).toBlob();f.toPng=async(e,t)=>(await f.capture(e,t)).toPng(t);f.toJpg=async(e,t)=>(await f.capture(e,t)).toJpg(t);f.toWebp=async(e,t)=>(await f.capture(e,t)).toWebp(t);f.download=async(e,t={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=t;return await(await f.capture(e,r)).download({format:o,filename:n,backgroundColor:a})};async function ce(e=document,t={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=t;if(n){S.clear(),E.clear(),$.clear(),k.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];e?.querySelectorAll&&(r=Array.from(e.querySelectorAll("img[src]")),s=Array.from(e.querySelectorAll("*")));let c=[];for(let i of r){let m=i.src;S.has(m)||c.push(C(m).then(l=>S.set(m,l)).catch(()=>{}))}for(let i of s){let m=getComputedStyle(i).backgroundImage,l=X(m);l&&!E.has(l)&&c.push(C(l).then(d=>E.set(l,d)).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,T=new Map,k=new Map,R=new WeakMap;var re=["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 e of re)z(e)}function z(e){if(T.has(e))return T.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let s={};return T.set(e,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(e);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),T.set(e,r),r}function U(e,t,o=!1){let n=[],a=z(t);for(let[r,s]of Object.entries(e))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(e){let t=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&t.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>t.add(o.tagName.toLowerCase())),Array.from(t))}function K(e){let t=new Map;for(let n of e){let a=T.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");t.has(r)||t.set(r,[]),t.get(r).push(n)}let o="";for(let[n,a]of t.entries())o+=`${a.join(",")} { ${n} }
|
|
2
|
+
`;return o}function J(e){let t=new Set(e.values()),o=new Map,n=1;for(let a of t)o.set(a,`c${n++}`);return o}function N(e,{fast:t=!1}={}){if(t)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function A(e,t=null){if(!(e instanceof Element))return window.getComputedStyle(e,t);let o=R.get(e);if(o||(o=new Map,R.set(e,o)),!o.has(t)){let n=window.getComputedStyle(e,t);o.set(t,n)}return o.get(t)}function Y(e){let t=e.replace(/^['"]|['"]$/g,"");if(t.startsWith("\\"))try{return String.fromCharCode(parseInt(t.replace("\\",""),16))}catch{return t}return t}function X(e){let t=e.match(/url\(["']?([^"')]+)["']?\)/);return t?t[1]:null}function B(e){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(e))}function C(e,t=3e3){return S.has(e)?Promise.resolve(S.get(e)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},t),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(e,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=e})}function M(e){let t={};for(let o of e)t[o]=e.getPropertyValue(o);return t}function D(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(e,t,o,n,a){if(e.tagName==="STYLE")return;n.has(e)||n.set(e,A(e));let r=n.get(e),s=M(r),c=e.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(t,i)}function F(e,t,o,n,a){if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=e.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(e.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${e.offsetWidth}px; height: ${e.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(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="placeholder"){let c=e.cloneNode(!1);n.set(c,e),O(e,c,t,o,a);let i=document.createElement("div");return i.textContent=e.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(e.tagName==="CANVAS"){let c=e.toDataURL(),i=document.createElement("img");return i.src=c,i.width=e.width,i.height=e.height,i.style.display="inline-block",i.style.width=`${e.width}px`,i.style.height=`${e.height}px`,i}if(e.nodeType===Node.TEXT_NODE){if(!e.textContent.trim())return null;if(e.parentElement?.shadowRoot){let i=e.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return e.cloneNode(!0)}if(e.nodeType!==Node.ELEMENT_NODE)return e.cloneNode(!0);let r=e.cloneNode(!1);n.set(r,e),O(e,r,t,o,a);let s=document.createDocumentFragment();if(e.childNodes.forEach(c=>{let i=F(c,t,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),e.shadowRoot){let c=Array.from(e.shadowRoot.children).filter(m=>m.tagName!=="STYLE").map(m=>F(m,t,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(m=>i.appendChild(m)),r.appendChild(i)}return r}async function G(e,t,o,n=32,a="#000"){t=t.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${t}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${t}"`:` ${n}px "${t}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(e,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:e=!0,preCached:t=!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(e&&(B(a.href)||B(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 d=l[1].replace(/["']/g,"");if(d.startsWith("http")||(d=new URL(d,a.href).href),e&&B(d))return console.log("\u23ED\uFE0F Ignorando icon font URL:",d),null;if($.has(d))return{original:l[0],inlined:`url(${$.get(d)})`};try{let h=await(await fetch(d)).blob(),p=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(d,p),{original:l[0],inlined:`url(${p})`}}catch{return console.warn("\u274C No pude fetch font",d),null}})),m=s;for(let l of i)l&&(m=m.replace(l.original,l.inlined));n+=m+`
|
|
3
|
+
`}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&t){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(e,t,o,n,a){if(!(e instanceof Element)||!(t instanceof Element))return;for(let c of["::before","::after"])try{let i=A(e,c);if(!i)continue;let m=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),d=m&&m!=="none"&&m!=='""'&&m!=="''",g=l&&l.startsWith("url(");if(d||g){let h=i.getPropertyValue("font-family"),p=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 P=M(i),v=U(P,"span",a);o.set(y,v);let L=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(m);if(L&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,p,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`,e,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?t.insertBefore(y,t.firstChild):t.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,e,i)}let r=Array.from(e.children),s=Array.from(t.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(e,t=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=F(e,o,n,a,t)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(e,r,o,n,t)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,m])=>`.${m}{${i}}`).join("");for(let[i,m]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(m);l&&i.classList.add(l);let d=i.style?.backgroundImage;i.removeAttribute("style"),d&&d!=="none"&&(i.style.backgroundImage=d)}return{clone:r,classCSS:c,styleCache:n}}async function Z(e){let t=Array.from(e.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<t.length;n+=4){let a=t.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function ee(e,t,o){let n=[[e,t]];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 d=l[1],g;E.has(d)?g=E.get(d):(g=await C(d),E.set(d,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),m=Array.from(r.children);for(let l=0;l<Math.min(i.length,m.length);l++)n.push([i[l],m[l]])}}async function te(e,t={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=t,s,c,i,m="",l="",d,g;if({clone:s,classCSS:c,styleCache:i}=await Q(e,o),await new Promise(p=>{N(async()=>{await Z(s),p()},{fast:a})}),await new Promise(p=>{N(async()=>{await ee(e,s,i),p()},{fast:a})}),n&&await new Promise(p=>{N(async()=>{m=await j({ignoreIconFonts:!0}),p()},{fast:a})}),o){let p=H(s).sort(),u=p.join(",");k.has(u)?l=k.get(u):await new Promise(w=>{N(()=>{l=K(p),k.set(u,l),w()},{fast:a})})}await new Promise(p=>{N(()=>{let u=e.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&&D()&&(s.style.scale=`${r}`);let P="http://www.w3.org/2000/svg",v=document.createElementNS(P,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let L=document.createElement("style");L.textContent=l+m+"svg{overflow:visible;}"+c,v.appendChild(L),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${P}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",d=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,p()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),d}async function se(e,{dpr:t=1,scale:o=1}){let n=new Image;return n.src=e,await n.decode(),D?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function ne(e,{dpr:t=1,scale:o=1}){let n=new Image;n.src=e,await n.decode();let a=document.createElement("canvas"),r=n.width*o*t,s=n.height*o*t;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*t,o*t),c.drawImage(n,0,0),a}async function oe(e){let t=decodeURIComponent(e.split(",")[1]);return new Blob([t],{type:"image/svg+xml"})}async function ae(e,{dpr:t=1,scale:o=1},n){let a=await ne(e,{dpr:t,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(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await ae(e,{dpr:t,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/t}px`,c.style.height=`${s.height/t}px`,c}async function ie(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await oe(e),h=URL.createObjectURL(g),p=document.createElement("a");p.href=h,p.download=`${r}.svg`,p.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await ae(e,{dpr:t,scale:o},n??s),m={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(m),d=document.createElement("a");d.href=l,d.download=`${r}.${a}`,d.click()}async function f(e,t={}){if(t={scale:1,...t},!e)throw new Error("Element cannot be null or undefined");return await f.capture(e,t)}f.capture=async(e,t={})=>{let o=await te(e,t),n=window.devicePixelRatio||1,a=t.scale||1;return{url:o,options:t,toRaw:()=>o,toImg:()=>se(o,{dpr:n,scale:a}),toCanvas:()=>ne(o,{dpr:n,scale:a}),toBlob:()=>oe(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}={})=>ie(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};f.toRaw=async(e,t)=>(await f.capture(e,t)).toRaw();f.toImg=async(e,t)=>(await f.capture(e,t)).toImg();f.toCanvas=async(e,t)=>(await f.capture(e,t)).toCanvas();f.toBlob=async(e,t)=>(await f.capture(e,t)).toBlob();f.toPng=async(e,t)=>(await f.capture(e,t)).toPng(t);f.toJpg=async(e,t)=>(await f.capture(e,t)).toJpg(t);f.toWebp=async(e,t)=>(await f.capture(e,t)).toWebp(t);f.download=async(e,t={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=t;return await(await f.capture(e,r)).download({format:o,filename:n,backgroundColor:a})};async function ce(e=document,t={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=t;if(n){S.clear(),E.clear(),$.clear(),k.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];e?.querySelectorAll&&(r=Array.from(e.querySelectorAll("img[src]")),s=Array.from(e.querySelectorAll("*")));let c=[];for(let i of r){let m=i.src;S.has(m)||c.push(C(m).then(l=>S.set(m,l)).catch(()=>{}))}for(let i of s){let m=getComputedStyle(i).backgroundImage,l=X(m);l&&!E.has(l)&&c.push(C(l).then(d=>E.set(l,d)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}export{ce as preCache,f 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-pre.1747581859131
|
|
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-pre.1747581859131",
|
|
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",
|