Skip to content

Commit 011620a

Browse files
committed
增强图像处理功能,添加图像加载失败时的后备图像源支持,并记录原始图像尺寸以便于使用。更新类型定义以包含新选项。
1 parent 0d41b7e commit 011620a

3 files changed

Lines changed: 124 additions & 14 deletions

File tree

src/core/clone.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,25 @@ export function deepClone(node, styleMap, styleCache, nodeMap, compress, options
134134

135135
if (node.tagName === 'IMG') {
136136
freezeImgSrcset(node, clone);
137+
// Record original image dimensions for fallback usage when inlining fails
138+
try {
139+
const rect = node.getBoundingClientRect();
140+
let w = Math.round(rect.width || 0);
141+
let h = Math.round(rect.height || 0);
142+
if (!w || !h) {
143+
const computed = window.getComputedStyle(node);
144+
const cssW = parseFloat(computed.width) || 0;
145+
const cssH = parseFloat(computed.height) || 0;
146+
const attrW = parseInt(node.getAttribute('width') || '', 10) || 0;
147+
const attrH = parseInt(node.getAttribute('height') || '', 10) || 0;
148+
const propW = node.width || node.naturalWidth || 0;
149+
const propH = node.height || node.naturalHeight || 0;
150+
w = Math.round(w || cssW || attrW || propW || 0);
151+
h = Math.round(h || cssH || attrH || propH || 0);
152+
}
153+
if (w) clone.dataset.snapdomWidth = String(w);
154+
if (h) clone.dataset.snapdomHeight = String(h);
155+
} catch {}
137156
}
138157
} catch (err) {
139158
console.error("[Snapdom] Failed to clone node:", node, err);

src/modules/images.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55

66
import { fetchImage } from '../utils/helpers.js';
77

8+
9+
function setImgPlaceholder(img) {
10+
const dsW = parseInt(img.dataset?.snapdomWidth || '', 10) || 0;
11+
const dsH = parseInt(img.dataset?.snapdomHeight || '', 10) || 0;
12+
const attrW = parseInt(img.getAttribute('width') || '', 10) || 0;
13+
const attrH = parseInt(img.getAttribute('height') || '', 10) || 0;
14+
const styleW = parseFloat(img.style?.width || '') || 0;
15+
const styleH = parseFloat(img.style?.height || '') || 0;
16+
const w = dsW || styleW || attrW || img.width || 100;
17+
const h = dsH || styleH || attrH || img.height || 100;
18+
19+
const fallback = document.createElement("div");
20+
fallback.style = `width: ${w}px; height: ${h}px; background: #ccc; display: inline-block; text-align: center; line-height: ${h}px; color: #666; font-size: 12px;`;
21+
fallback.innerText = "img";
22+
img.replaceWith(fallback);
23+
}
24+
825
/**
926
* Converts all <img> elements in the clone to data URLs or replaces them with placeholders if loading fails.
1027
*
@@ -19,6 +36,7 @@ export async function inlineImages(clone, options = {}) {
1936
const eff = img.currentSrc || img.src || '';
2037
if (eff) img.setAttribute('src', eff);
2138
}
39+
2240
img.removeAttribute('srcset');
2341
img.removeAttribute('sizes');
2442
const src = img.src;
@@ -28,10 +46,36 @@ export async function inlineImages(clone, options = {}) {
2846
if (!img.width) img.width = img.naturalWidth || 100;
2947
if (!img.height) img.height = img.naturalHeight || 100;
3048
} catch {
31-
const fallback = document.createElement("div");
32-
fallback.style = `width: ${img.width || 100}px; height: ${img.height || 100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${img.height || 100}px; color: #666; font-size: 12px;`;
33-
fallback.innerText = "img";
34-
img.replaceWith(fallback);
49+
// Try defaultImageUrl (string or callback)
50+
const { defaultImageUrl } = options || {};
51+
if (defaultImageUrl) {
52+
try {
53+
const dsW = parseInt(img.dataset?.snapdomWidth || '', 10) || 0;
54+
const dsH = parseInt(img.dataset?.snapdomHeight || '', 10) || 0;
55+
const attrW = parseInt(img.getAttribute('width') || '', 10) || 0;
56+
const attrH = parseInt(img.getAttribute('height') || '', 10) || 0;
57+
const styleW = parseFloat(img.style?.width || '') || 0;
58+
const styleH = parseFloat(img.style?.height || '') || 0;
59+
const width = dsW || styleW || attrW || img.width || undefined;
60+
const height = dsH || styleH || attrH || img.height || undefined;
61+
62+
const fallbackUrl = typeof defaultImageUrl === 'function'
63+
? await defaultImageUrl({ width, height, src, element: img })
64+
: defaultImageUrl;
65+
66+
if (fallbackUrl) {
67+
const fallbackData = await fetchImage(fallbackUrl, { useProxy: options.useProxy });
68+
img.src = fallbackData;
69+
if (!img.width && width) img.width = width;
70+
if (!img.height && height) img.height = height;
71+
if (!img.width) img.width = img.naturalWidth || 100;
72+
if (!img.height) img.height = img.naturalHeight || 100;
73+
return;
74+
}
75+
} catch {}
76+
}
77+
78+
setImgPlaceholder(img);
3579
}
3680
};
3781
for (let i = 0; i < imgs.length; i += 4) {

types/snapdom.d.ts

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ declare module "@zumer/snapdom" {
22
export interface SnapOptions {
33
compress?: boolean;
44
embedFonts?: boolean;
5-
localFonts?: Array<{ family: string; src: string; weight?: string; style?: string }>;
5+
localFonts?: Array<{
6+
family: string;
7+
src: string;
8+
weight?: string;
9+
style?: string;
10+
}>;
611
iconFonts?: string | RegExp | Array<string | RegExp>;
712
fast?: boolean;
813
scale?: number;
@@ -15,6 +20,19 @@ declare module "@zumer/snapdom" {
1520
dpr?: number;
1621
quality?: number;
1722
useProxy?: string;
23+
/**
24+
* Fallback image source when an <img> fails to load.
25+
* - String: use as-is.
26+
* - Callback: receives measured width/height and original src, returns a URL string.
27+
*/
28+
defaultImageUrl?:
29+
| string
30+
| ((args: {
31+
width?: number;
32+
height?: number;
33+
src?: string;
34+
element: HTMLImageElement;
35+
}) => string | Promise<string>);
1836
exclude?: string[];
1937
filter?: (element: Element, originalElement: Element) => boolean;
2038
}
@@ -41,15 +59,39 @@ declare module "@zumer/snapdom" {
4159
): Promise<SnapResult>;
4260

4361
export namespace snapdom {
44-
function capture(element: HTMLElement, options?: SnapOptions): Promise<SnapResult>;
45-
function toRaw(element: HTMLElement, options?: SnapOptions): Promise<string>;
46-
function toImg(element: HTMLElement, options?: SnapOptions): Promise<HTMLImageElement>;
47-
function toCanvas(element: HTMLElement, options?: SnapOptions): Promise<HTMLCanvasElement>;
62+
function capture(
63+
element: HTMLElement,
64+
options?: SnapOptions
65+
): Promise<SnapResult>;
66+
function toRaw(
67+
element: HTMLElement,
68+
options?: SnapOptions
69+
): Promise<string>;
70+
function toImg(
71+
element: HTMLElement,
72+
options?: SnapOptions
73+
): Promise<HTMLImageElement>;
74+
function toCanvas(
75+
element: HTMLElement,
76+
options?: SnapOptions
77+
): Promise<HTMLCanvasElement>;
4878
function toBlob(element: HTMLElement, options?: SnapOptions): Promise<Blob>;
49-
function toPng(element: HTMLElement, options?: SnapOptions): Promise<HTMLImageElement>;
50-
function toJpg(element: HTMLElement, options?: SnapOptions): Promise<HTMLImageElement>;
51-
function toWebp(element: HTMLElement, options?: SnapOptions): Promise<HTMLImageElement>;
52-
function download(element: HTMLElement, options?: SnapOptions): Promise<void>;
79+
function toPng(
80+
element: HTMLElement,
81+
options?: SnapOptions
82+
): Promise<HTMLImageElement>;
83+
function toJpg(
84+
element: HTMLElement,
85+
options?: SnapOptions
86+
): Promise<HTMLImageElement>;
87+
function toWebp(
88+
element: HTMLElement,
89+
options?: SnapOptions
90+
): Promise<HTMLImageElement>;
91+
function download(
92+
element: HTMLElement,
93+
options?: SnapOptions
94+
): Promise<void>;
5395
}
5496

5597
/**
@@ -61,7 +103,12 @@ declare module "@zumer/snapdom" {
61103
embedFonts?: boolean;
62104
useProxy?: string;
63105
reset?: boolean;
64-
localFonts?: Array<{ family: string; src: string; weight?: string; style?: string }>;
106+
localFonts?: Array<{
107+
family: string;
108+
src: string;
109+
weight?: string;
110+
style?: string;
111+
}>;
65112
}
66113
): Promise<void>;
67114
}

0 commit comments

Comments
 (0)