@zumer/snapdom 0.9.8 → 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 +120 -86
- package/dist/snapdom.min.js +3 -3
- package/dist/snapdom.min.mjs +3 -3
- package/dist/snapdom.mjs +120 -86
- 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
|
**/
|
|
@@ -112,12 +112,15 @@
|
|
|
112
112
|
}
|
|
113
113
|
function collectUsedTagNames(root) {
|
|
114
114
|
const tagSet = /* @__PURE__ */ new Set();
|
|
115
|
+
if (root.nodeType !== Node.ELEMENT_NODE && root.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
115
118
|
if (root.tagName) {
|
|
116
119
|
tagSet.add(root.tagName.toLowerCase());
|
|
117
120
|
}
|
|
118
|
-
root.querySelectorAll
|
|
119
|
-
tagSet.add(el.tagName.toLowerCase());
|
|
120
|
-
}
|
|
121
|
+
if (typeof root.querySelectorAll === "function") {
|
|
122
|
+
root.querySelectorAll("*").forEach((el) => tagSet.add(el.tagName.toLowerCase()));
|
|
123
|
+
}
|
|
121
124
|
return Array.from(tagSet);
|
|
122
125
|
}
|
|
123
126
|
function generateDedupedBaseCSS(usedTagNames) {
|
|
@@ -244,6 +247,9 @@
|
|
|
244
247
|
}
|
|
245
248
|
return snap;
|
|
246
249
|
}
|
|
250
|
+
function isSafari() {
|
|
251
|
+
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
252
|
+
}
|
|
247
253
|
|
|
248
254
|
// src/modules/styles.js
|
|
249
255
|
function inlineAllStyles(source, clone, styleMap, cache, compress) {
|
|
@@ -288,6 +294,9 @@
|
|
|
288
294
|
img.src = dataURL;
|
|
289
295
|
img.width = node.width;
|
|
290
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`;
|
|
291
300
|
return img;
|
|
292
301
|
}
|
|
293
302
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
@@ -593,14 +602,12 @@
|
|
|
593
602
|
await new Promise((resolve) => {
|
|
594
603
|
idle(() => {
|
|
595
604
|
const rect = element.getBoundingClientRect();
|
|
596
|
-
const w = rect.width
|
|
597
|
-
const h = rect.height
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
clone.style.
|
|
602
|
-
clone.style.width = `${rect.width}px`;
|
|
603
|
-
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}`;
|
|
604
611
|
}
|
|
605
612
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
606
613
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
@@ -625,68 +632,122 @@
|
|
|
625
632
|
}
|
|
626
633
|
|
|
627
634
|
// src/api/snapdom.js
|
|
628
|
-
async function
|
|
629
|
-
|
|
630
|
-
|
|
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;
|
|
631
645
|
}
|
|
632
|
-
return
|
|
646
|
+
return img;
|
|
633
647
|
}
|
|
634
|
-
|
|
635
|
-
const url = await snapdom(el, options);
|
|
648
|
+
async function toCanvas(url, { dpr = 1, scale = 1 }) {
|
|
636
649
|
const img = new Image();
|
|
637
650
|
img.src = url;
|
|
638
651
|
await img.decode();
|
|
639
|
-
return img;
|
|
640
|
-
};
|
|
641
|
-
snapdom.toCanvas = async (el, options = {}) => {
|
|
642
|
-
const img = await snapdom.toImg(el, options);
|
|
643
652
|
const canvas = document.createElement("canvas");
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
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);
|
|
647
660
|
return canvas;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
snapdom.toJpg = async (el, options = {}) => {
|
|
657
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
658
|
-
const canvas = await snapdom.toCanvas(el, options);
|
|
659
|
-
const temp = document.createElement("canvas");
|
|
660
|
-
temp.width = canvas.width;
|
|
661
|
-
temp.height = canvas.height;
|
|
662
|
-
const ctx = temp.getContext("2d");
|
|
663
|
-
ctx.fillStyle = backgroundColor;
|
|
664
|
-
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
665
|
-
ctx.drawImage(canvas, 0, 0);
|
|
666
|
-
const img = new Image();
|
|
667
|
-
img.src = temp.toDataURL("image/jpeg", quality);
|
|
668
|
-
await img.decode();
|
|
669
|
-
return img;
|
|
670
|
-
};
|
|
671
|
-
snapdom.toWebp = async (el, options = {}) => {
|
|
672
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
673
|
-
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;
|
|
674
669
|
const temp = document.createElement("canvas");
|
|
675
|
-
temp.width =
|
|
676
|
-
temp.height =
|
|
670
|
+
temp.width = baseCanvas.width;
|
|
671
|
+
temp.height = baseCanvas.height;
|
|
677
672
|
const ctx = temp.getContext("2d");
|
|
678
673
|
ctx.fillStyle = backgroundColor;
|
|
679
674
|
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
680
|
-
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);
|
|
681
680
|
const img = new Image();
|
|
682
|
-
img.src =
|
|
681
|
+
img.src = canvas.toDataURL(`image/${format}`, quality);
|
|
683
682
|
await img.decode();
|
|
683
|
+
img.style.width = `${canvas.width / dpr}px`;
|
|
684
|
+
img.style.height = `${canvas.height / dpr}px`;
|
|
684
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
|
+
};
|
|
685
734
|
};
|
|
686
|
-
snapdom.
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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 });
|
|
690
751
|
};
|
|
691
752
|
|
|
692
753
|
// src/api/preCache.js
|
|
@@ -731,32 +792,5 @@
|
|
|
731
792
|
await embedCustomFonts({ ignoreIconFonts: true, preCached: true });
|
|
732
793
|
}
|
|
733
794
|
await Promise.all(promises);
|
|
734
|
-
if (preWarm) {
|
|
735
|
-
const usedTags = collectUsedTagNames(root).sort();
|
|
736
|
-
const tagKey = usedTags.join(",");
|
|
737
|
-
if (!baseCSSCache.has(tagKey)) {
|
|
738
|
-
const css = generateDedupedBaseCSS(usedTags);
|
|
739
|
-
baseCSSCache.set(tagKey, css);
|
|
740
|
-
}
|
|
741
|
-
const clone = root.cloneNode(false);
|
|
742
|
-
await inlinePseudoElements(
|
|
743
|
-
root,
|
|
744
|
-
clone,
|
|
745
|
-
/* @__PURE__ */ new Map(),
|
|
746
|
-
/* @__PURE__ */ new WeakMap(),
|
|
747
|
-
/* compress */
|
|
748
|
-
false
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (preWarm) {
|
|
752
|
-
try {
|
|
753
|
-
await prepareClone(
|
|
754
|
-
root,
|
|
755
|
-
/* compress: */
|
|
756
|
-
options.preWarm
|
|
757
|
-
);
|
|
758
|
-
} catch (e) {
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
795
|
}
|
|
762
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
|
**/
|
|
@@ -112,12 +112,15 @@ function getStyleKey(snapshot, tagName, compress = false) {
|
|
|
112
112
|
}
|
|
113
113
|
function collectUsedTagNames(root) {
|
|
114
114
|
const tagSet = /* @__PURE__ */ new Set();
|
|
115
|
+
if (root.nodeType !== Node.ELEMENT_NODE && root.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
115
118
|
if (root.tagName) {
|
|
116
119
|
tagSet.add(root.tagName.toLowerCase());
|
|
117
120
|
}
|
|
118
|
-
root.querySelectorAll
|
|
119
|
-
tagSet.add(el.tagName.toLowerCase());
|
|
120
|
-
}
|
|
121
|
+
if (typeof root.querySelectorAll === "function") {
|
|
122
|
+
root.querySelectorAll("*").forEach((el) => tagSet.add(el.tagName.toLowerCase()));
|
|
123
|
+
}
|
|
121
124
|
return Array.from(tagSet);
|
|
122
125
|
}
|
|
123
126
|
function generateDedupedBaseCSS(usedTagNames) {
|
|
@@ -244,6 +247,9 @@ function snapshotComputedStyle(style) {
|
|
|
244
247
|
}
|
|
245
248
|
return snap;
|
|
246
249
|
}
|
|
250
|
+
function isSafari() {
|
|
251
|
+
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
252
|
+
}
|
|
247
253
|
|
|
248
254
|
// src/modules/styles.js
|
|
249
255
|
function inlineAllStyles(source, clone, styleMap, cache, compress) {
|
|
@@ -288,6 +294,9 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress) {
|
|
|
288
294
|
img.src = dataURL;
|
|
289
295
|
img.width = node.width;
|
|
290
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`;
|
|
291
300
|
return img;
|
|
292
301
|
}
|
|
293
302
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
@@ -593,14 +602,12 @@ async function captureDOM(element, options = {}) {
|
|
|
593
602
|
await new Promise((resolve) => {
|
|
594
603
|
idle(() => {
|
|
595
604
|
const rect = element.getBoundingClientRect();
|
|
596
|
-
const w = rect.width
|
|
597
|
-
const h = rect.height
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
clone.style.
|
|
602
|
-
clone.style.width = `${rect.width}px`;
|
|
603
|
-
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}`;
|
|
604
611
|
}
|
|
605
612
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
606
613
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
@@ -625,68 +632,122 @@ async function captureDOM(element, options = {}) {
|
|
|
625
632
|
}
|
|
626
633
|
|
|
627
634
|
// src/api/snapdom.js
|
|
628
|
-
async function
|
|
629
|
-
|
|
630
|
-
|
|
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;
|
|
631
645
|
}
|
|
632
|
-
return
|
|
646
|
+
return img;
|
|
633
647
|
}
|
|
634
|
-
|
|
635
|
-
const url = await snapdom(el, options);
|
|
648
|
+
async function toCanvas(url, { dpr = 1, scale = 1 }) {
|
|
636
649
|
const img = new Image();
|
|
637
650
|
img.src = url;
|
|
638
651
|
await img.decode();
|
|
639
|
-
return img;
|
|
640
|
-
};
|
|
641
|
-
snapdom.toCanvas = async (el, options = {}) => {
|
|
642
|
-
const img = await snapdom.toImg(el, options);
|
|
643
652
|
const canvas = document.createElement("canvas");
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
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);
|
|
647
660
|
return canvas;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
snapdom.toJpg = async (el, options = {}) => {
|
|
657
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
658
|
-
const canvas = await snapdom.toCanvas(el, options);
|
|
659
|
-
const temp = document.createElement("canvas");
|
|
660
|
-
temp.width = canvas.width;
|
|
661
|
-
temp.height = canvas.height;
|
|
662
|
-
const ctx = temp.getContext("2d");
|
|
663
|
-
ctx.fillStyle = backgroundColor;
|
|
664
|
-
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
665
|
-
ctx.drawImage(canvas, 0, 0);
|
|
666
|
-
const img = new Image();
|
|
667
|
-
img.src = temp.toDataURL("image/jpeg", quality);
|
|
668
|
-
await img.decode();
|
|
669
|
-
return img;
|
|
670
|
-
};
|
|
671
|
-
snapdom.toWebp = async (el, options = {}) => {
|
|
672
|
-
const { backgroundColor = "#fff", quality = 1 } = options;
|
|
673
|
-
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;
|
|
674
669
|
const temp = document.createElement("canvas");
|
|
675
|
-
temp.width =
|
|
676
|
-
temp.height =
|
|
670
|
+
temp.width = baseCanvas.width;
|
|
671
|
+
temp.height = baseCanvas.height;
|
|
677
672
|
const ctx = temp.getContext("2d");
|
|
678
673
|
ctx.fillStyle = backgroundColor;
|
|
679
674
|
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
680
|
-
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);
|
|
681
680
|
const img = new Image();
|
|
682
|
-
img.src =
|
|
681
|
+
img.src = canvas.toDataURL(`image/${format}`, quality);
|
|
683
682
|
await img.decode();
|
|
683
|
+
img.style.width = `${canvas.width / dpr}px`;
|
|
684
|
+
img.style.height = `${canvas.height / dpr}px`;
|
|
684
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
|
+
};
|
|
685
734
|
};
|
|
686
|
-
snapdom.
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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 });
|
|
690
751
|
};
|
|
691
752
|
|
|
692
753
|
// src/api/preCache.js
|
|
@@ -731,33 +792,6 @@ async function preCache(root = document, options = {}) {
|
|
|
731
792
|
await embedCustomFonts({ ignoreIconFonts: true, preCached: true });
|
|
732
793
|
}
|
|
733
794
|
await Promise.all(promises);
|
|
734
|
-
if (preWarm) {
|
|
735
|
-
const usedTags = collectUsedTagNames(root).sort();
|
|
736
|
-
const tagKey = usedTags.join(",");
|
|
737
|
-
if (!baseCSSCache.has(tagKey)) {
|
|
738
|
-
const css = generateDedupedBaseCSS(usedTags);
|
|
739
|
-
baseCSSCache.set(tagKey, css);
|
|
740
|
-
}
|
|
741
|
-
const clone = root.cloneNode(false);
|
|
742
|
-
await inlinePseudoElements(
|
|
743
|
-
root,
|
|
744
|
-
clone,
|
|
745
|
-
/* @__PURE__ */ new Map(),
|
|
746
|
-
/* @__PURE__ */ new WeakMap(),
|
|
747
|
-
/* compress */
|
|
748
|
-
false
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (preWarm) {
|
|
752
|
-
try {
|
|
753
|
-
await prepareClone(
|
|
754
|
-
root,
|
|
755
|
-
/* compress: */
|
|
756
|
-
options.preWarm
|
|
757
|
-
);
|
|
758
|
-
} catch (e) {
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
795
|
}
|
|
762
796
|
export {
|
|
763
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",
|