fix: generate html image in safari (#123)

This commit is contained in:
boojack 2022-07-17 01:52:29 +08:00 committed by GitHub
parent 2a1e34fe03
commit 167e5596f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 45 deletions

View File

@ -1,34 +1,33 @@
import convertResourceToDataURL from "./convertResourceToDataURL"; import convertResourceToDataURL from "./convertResourceToDataURL";
const applyStyles = async (sourceElement: HTMLElement, clonedElement: HTMLElement) => {
if (!sourceElement || !clonedElement) {
return;
}
if (sourceElement.tagName === "IMG") {
try {
const url = await convertResourceToDataURL(sourceElement.getAttribute("src") ?? "");
(clonedElement as HTMLImageElement).src = url;
} catch (error) {
// do nth
}
}
const sourceStyles = window.getComputedStyle(sourceElement);
for (const item of sourceStyles) {
clonedElement.style.setProperty(item, sourceStyles.getPropertyValue(item), sourceStyles.getPropertyPriority(item));
}
for (let i = 0; i < clonedElement.childElementCount; i++) {
await applyStyles(sourceElement.children[i] as HTMLElement, clonedElement.children[i] as HTMLElement);
}
};
const getCloneStyledElement = async (element: HTMLElement) => { const getCloneStyledElement = async (element: HTMLElement) => {
const clonedElementContainer = document.createElement(element.tagName); const clonedElementContainer = document.createElement(element.tagName);
clonedElementContainer.innerHTML = element.innerHTML; clonedElementContainer.innerHTML = element.innerHTML;
const applyStyles = async (sourceElement: HTMLElement, clonedElement: HTMLElement) => {
if (!sourceElement || !clonedElement) {
return;
}
const sourceStyles = window.getComputedStyle(sourceElement);
if (sourceElement.tagName === "IMG") {
try {
const url = await convertResourceToDataURL(sourceElement.getAttribute("src") ?? "");
(clonedElement as HTMLImageElement).src = url;
} catch (error) {
// do nth
}
}
for (const item of sourceStyles) {
clonedElement.style.setProperty(item, sourceStyles.getPropertyValue(item), sourceStyles.getPropertyPriority(item));
}
for (let i = 0; i < clonedElement.childElementCount; i++) {
await applyStyles(sourceElement.children[i] as HTMLElement, clonedElement.children[i] as HTMLElement);
}
};
await applyStyles(element, clonedElementContainer); await applyStyles(element, clonedElementContainer);
return clonedElementContainer; return clonedElementContainer;

View File

@ -6,6 +6,7 @@
* 2. <foreignObject>: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject * 2. <foreignObject>: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject
*/ */
import getCloneStyledElement from "./getCloneStyledElement"; import getCloneStyledElement from "./getCloneStyledElement";
import waitImageLoaded from "./waitImageLoaded";
type Options = Partial<{ type Options = Partial<{
backgroundColor: string; backgroundColor: string;
@ -51,7 +52,6 @@ const generateSVGElement = (width: number, height: number, element: HTMLElement)
export const toSVG = async (element: HTMLElement, options?: Options) => { export const toSVG = async (element: HTMLElement, options?: Options) => {
const { width, height } = getElementSize(element); const { width, height } = getElementSize(element);
const clonedElement = await getCloneStyledElement(element); const clonedElement = await getCloneStyledElement(element);
if (options?.backgroundColor) { if (options?.backgroundColor) {
@ -65,11 +65,6 @@ export const toSVG = async (element: HTMLElement, options?: Options) => {
}; };
export const toCanvas = async (element: HTMLElement, options?: Options): Promise<HTMLCanvasElement> => { export const toCanvas = async (element: HTMLElement, options?: Options): Promise<HTMLCanvasElement> => {
const url = await toSVG(element, options);
const imageEl = new Image();
imageEl.src = url;
const ratio = options?.pixelRatio || 1; const ratio = options?.pixelRatio || 1;
const { width, height } = getElementSize(element); const { width, height } = getElementSize(element);
@ -91,18 +86,20 @@ export const toCanvas = async (element: HTMLElement, options?: Options): Promise
context.fillRect(0, 0, canvas.width, canvas.height); context.fillRect(0, 0, canvas.width, canvas.height);
} }
return new Promise((resolve) => { const url = await toSVG(element, options);
imageEl.onload = () => { const imageEl = new Image();
context.drawImage(imageEl, 0, 0, canvas.width, canvas.height); imageEl.style.zIndex = "-1";
imageEl.style.position = "absolute";
resolve(canvas); imageEl.style.top = "0";
}; document.body.append(imageEl);
}); await waitImageLoaded(imageEl, url);
context.drawImage(imageEl, 0, 0, canvas.width, canvas.height);
imageEl.remove();
return canvas;
}; };
const toImage = async (element: HTMLElement, options?: Options) => { const toImage = async (element: HTMLElement, options?: Options) => {
const canvas = await toCanvas(element, options); const canvas = await toCanvas(element, options);
return canvas.toDataURL(); return canvas.toDataURL();
}; };

View File

@ -0,0 +1,17 @@
const waitImageLoaded = (image: HTMLImageElement, url: string): Promise<void> => {
return new Promise((resolve, reject) => {
image.loading = "eager";
image.onload = () => {
// NOTE: There is image loading problem in Safari, fix it with some trick
setTimeout(() => {
resolve();
}, 200);
};
image.onerror = () => {
reject("Image load failed");
};
image.src = url;
});
};
export default waitImageLoaded;

View File

@ -63,14 +63,14 @@
@apply flex flex-row justify-start items-center w-full py-3 px-6; @apply flex flex-row justify-start items-center w-full py-3 px-6;
> .normal-text { > .normal-text {
@apply w-full flex flex-row justify-start items-center text-sm text-gray-500; @apply w-full flex flex-row justify-start items-center text-sm leading-6 text-gray-500;
> .name-text {
@apply text-black ml-2;
}
> .icon-text { > .icon-text {
@apply text-lg ml-1 mr-2; @apply text-lg ml-1 mr-2 leading-6;
}
> .name-text {
@apply text-black ml-2 leading-6;
} }
} }
} }