From 167e5596f270384448765b56dcb0b2548f27963f Mon Sep 17 00:00:00 2001 From: boojack Date: Sun, 17 Jul 2022 01:52:29 +0800 Subject: [PATCH] fix: generate html image in safari (#123) --- .../labs/html2image/getCloneStyledElement.ts | 49 +++++++++---------- web/src/labs/html2image/index.ts | 25 +++++----- web/src/labs/html2image/waitImageLoaded.ts | 17 +++++++ web/src/less/share-memo-image-dialog.less | 12 ++--- 4 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 web/src/labs/html2image/waitImageLoaded.ts diff --git a/web/src/labs/html2image/getCloneStyledElement.ts b/web/src/labs/html2image/getCloneStyledElement.ts index c5b6ac8c..35de3b79 100644 --- a/web/src/labs/html2image/getCloneStyledElement.ts +++ b/web/src/labs/html2image/getCloneStyledElement.ts @@ -1,34 +1,33 @@ 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 clonedElementContainer = document.createElement(element.tagName); 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); return clonedElementContainer; diff --git a/web/src/labs/html2image/index.ts b/web/src/labs/html2image/index.ts index 1392b30f..c32f4644 100644 --- a/web/src/labs/html2image/index.ts +++ b/web/src/labs/html2image/index.ts @@ -6,6 +6,7 @@ * 2. : https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject */ import getCloneStyledElement from "./getCloneStyledElement"; +import waitImageLoaded from "./waitImageLoaded"; type Options = Partial<{ backgroundColor: string; @@ -51,7 +52,6 @@ const generateSVGElement = (width: number, height: number, element: HTMLElement) export const toSVG = async (element: HTMLElement, options?: Options) => { const { width, height } = getElementSize(element); - const clonedElement = await getCloneStyledElement(element); if (options?.backgroundColor) { @@ -65,11 +65,6 @@ export const toSVG = async (element: HTMLElement, options?: Options) => { }; export const toCanvas = async (element: HTMLElement, options?: Options): Promise => { - const url = await toSVG(element, options); - - const imageEl = new Image(); - imageEl.src = url; - const ratio = options?.pixelRatio || 1; 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); } - return new Promise((resolve) => { - imageEl.onload = () => { - context.drawImage(imageEl, 0, 0, canvas.width, canvas.height); - - resolve(canvas); - }; - }); + const url = await toSVG(element, options); + const imageEl = new Image(); + imageEl.style.zIndex = "-1"; + imageEl.style.position = "absolute"; + 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 canvas = await toCanvas(element, options); - return canvas.toDataURL(); }; diff --git a/web/src/labs/html2image/waitImageLoaded.ts b/web/src/labs/html2image/waitImageLoaded.ts new file mode 100644 index 00000000..21263381 --- /dev/null +++ b/web/src/labs/html2image/waitImageLoaded.ts @@ -0,0 +1,17 @@ +const waitImageLoaded = (image: HTMLImageElement, url: string): Promise => { + 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; diff --git a/web/src/less/share-memo-image-dialog.less b/web/src/less/share-memo-image-dialog.less index 9cce2af5..721a0645 100644 --- a/web/src/less/share-memo-image-dialog.less +++ b/web/src/less/share-memo-image-dialog.less @@ -63,14 +63,14 @@ @apply flex flex-row justify-start items-center w-full py-3 px-6; > .normal-text { - @apply w-full flex flex-row justify-start items-center text-sm text-gray-500; - - > .name-text { - @apply text-black ml-2; - } + @apply w-full flex flex-row justify-start items-center text-sm leading-6 text-gray-500; > .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; } } }