From d5c17e1cf1cdf4afd1ad2b560fbe8d467d608947 Mon Sep 17 00:00:00 2001 From: hzsrc Date: Fri, 16 Aug 2024 15:31:51 +0800 Subject: [PATCH 01/21] feat: add option to resolve problems which caused by html that has large amount of sub nodes --- README.md | 9 +++++++++ src/clone-node.ts | 12 ++++++------ src/index.ts | 2 +- src/types.ts | 5 +++++ src/util.ts | 27 +++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 782d2525..1b1e0a8b 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,15 @@ A string indicating the image format. The default type is image/png; that type i When supplied, the toCanvas function will return a blob matching the given image type and quality. Defaults to `image/png` + + +### usePageCss + +Use `true` to add a `\n' + node.outerHTML + '' +} + export async function toImage( node: T, options: Options = {}, diff --git a/src/util.ts b/src/util.ts index f782d062..055bf962 100644 --- a/src/util.ts +++ b/src/util.ts @@ -311,22 +311,23 @@ export function createImage(url: string): Promise { export async function svgToDataURL(svg: SVGElement): Promise { return Promise.resolve() .then(async function() { - // svg中的图片地址无法离线下载,需要先转成dataUrl - const imgs = svg.querySelectorAll('img') - return Promise.all( - [].slice.call(imgs, 0).map((img: HTMLImageElement) => { - return urlToDataUrl(img.src).then((dataUrl) => (img.src = dataUrl)) - }), - ).then(() => { - const xml = new XMLSerializer().serializeToString(svg) - // open('about:blank').document.write('' + xml); //for debug - return xml - }) + const xml = new XMLSerializer().serializeToString(svg) + // open('about:blank').document.write('<plaintext>' + xml); //for debug + return xml }) .then(encodeURIComponent) .then((html) => `data:image/svg+xml;charset=utf-8,${html}`) } +export function setImgDataUrl(el: Element) { + const imgs = el.querySelectorAll('img') + return Promise.all( + [].slice.call(imgs, 0).map((img: HTMLImageElement) => { + return urlToDataUrl(img.src).then((dataUrl) => (img.src = dataUrl)) + }), + ) +} + const TailColor = 'fefffd' const TailHeight = 60 @@ -398,13 +399,13 @@ export function getStyles() { promises.push( fetch(href) .then((r) => r.text()) - .then((tx) => transRelPath(href, tx)) + .then((tx) => srcToDataUrl(href, tx)) .catch(() => ''), ) } else { promises.push( Promise.resolve( - transRelPath(window.location.href, (e as HTMLStyleElement).innerText), + srcToDataUrl(window.location.href, (e as HTMLStyleElement).innerText), ), ) } @@ -414,7 +415,7 @@ export function getStyles() { }) } -function transRelPath(cssPath: string, cssTextIn: string): Promise<string> { +function srcToDataUrl(cssPath: string, cssTextIn: string): Promise<string> { const cssText = cssTextIn.replace(/\/\*[\s\S]*?\*\//g, '') const quotReg = /^\s*(['"])(.+?)\1/ const map: { [url: string]: string } = {} From 39128702f57d8c95a1748a44d29b9ecf7e26b07e Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Wed, 20 Nov 2024 15:49:52 +0800 Subject: [PATCH 15/21] willReadFrequently: true --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9da3224e..8678d32a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,7 +64,7 @@ export async function toCanvas<T extends HTMLElement>( const img = await toImage(node, options) const { width, height } = getImageSize(node, options, img) const canvas = document.createElement('canvas') - const context = canvas.getContext('2d')! + const context = canvas.getContext('2d', { willReadFrequently: true })! const ratio = options.pixelRatio || getPixelRatio() const canvasWidth = options.canvasWidth || width const canvasHeight = options.canvasHeight || height @@ -107,7 +107,7 @@ export async function toCanvasList<T extends HTMLElement>( const scale = canvasWidth / img.width for (let curY = 0; curY < canvasHeight; curY += dimensionLimit) { const canvas = document.createElement('canvas') - const context = canvas.getContext('2d')! + const context = canvas.getContext('2d', { willReadFrequently: true })! const height1 = Math.min(canvasHeight - curY, dimensionLimit) canvas.width = canvasWidth canvas.height = height1 From d71ce44c34235f5ce0076fd8bb9a1da5aa36d208 Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Wed, 20 Nov 2024 15:59:39 +0800 Subject: [PATCH 16/21] await --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 8678d32a..13edf996 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,9 +44,10 @@ async function prepareNode(node: HTMLElement, options: Options = {}) { export async function toOfflineHtml(node: HTMLElement, options: Options = {}) { var node = await prepareNode(node, options) + var style = await getStyles() return '<!DOCTYPE html><html>' + '<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">' + - '<body><style>' + getStyles() + '</style>\n' + node.outerHTML + '</body></html>' + '<body><style>' + style + '</style>\n' + node.outerHTML + '</body></html>' } export async function toImage<T extends HTMLElement>( From da1953dea1a83d4f0bb4059401eb83c103ad46aa Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Mon, 13 Jan 2025 15:32:55 +0800 Subject: [PATCH 17/21] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=AB=98=E5=BA=A6?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/util.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9901851b..db356873 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-image-big", - "version": "1.11.12", + "version": "1.11.13", "description": "Generates an image from a DOM node using HTML5 canvas and SVG. Support big html page.", "main": "lib/index.js", "module": "es/index.js", diff --git a/src/util.ts b/src/util.ts index 055bf962..bc4c93e4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -341,7 +341,8 @@ export async function nodeToDataURL( const svg = document.createElementNS(xmlns, 'svg') const foreignObject = document.createElementNS(xmlns, 'foreignObject') // add a tail for check ending - const heightWithTail = height + TailHeight * 2 + let heightWithTail = height + if (opt.checkTail) heightWithTail += TailHeight * 2 // fix: if ratio=2 and style.border='1px', in html it is actually rendered to 1px, but in <img src="svg" alt="i"> it is rendered to 2px. Then height is different and the bottom 1px is lost, 10 nodes will lost 10px. const ratio = getPixelRatio() svg.setAttribute('width', `${width / ratio}`) From 705e6aaca9bc252e4f2c5849e8f2a54b70fbcdcf Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Mon, 13 Jan 2025 15:55:06 +0800 Subject: [PATCH 18/21] lint --- src/index.ts | 20 ++++++++------------ src/types.ts | 2 +- src/util.ts | 21 ++++++++++++--------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index 13edf996..dfca4e51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,6 @@ import { setImgDataUrl, } from './util' - export async function toSvg<T extends HTMLElement>( node: T, options: Options = {}, @@ -25,12 +24,7 @@ export async function toSvg<T extends HTMLElement>( // await embedWebFonts(clonedNode, options) // await embedImages(clonedNode, options) applyStyle(clonedNode, options) - const datauri = await nodeToDataURL( - clonedNode, - width, - height, - options, - ) + const datauri = await nodeToDataURL(clonedNode, width, height, options) return datauri } @@ -43,11 +37,13 @@ async function prepareNode(node: HTMLElement, options: Options = {}) { } export async function toOfflineHtml(node: HTMLElement, options: Options = {}) { - var node = await prepareNode(node, options) - var style = await getStyles() - return '<!DOCTYPE html><html>' + - '<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">' + - '<body><style>' + style + '</style>\n' + node.outerHTML + '</body></html>' + const node1 = await prepareNode(node, options) + const style = await getStyles() + return ( + `<!DOCTYPE html><html>` + + `<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">` + + `<body><style>${style}</style>\n${node1.outerHTML}</body></html>` + ) } export async function toImage<T extends HTMLElement>( diff --git a/src/types.ts b/src/types.ts index 2b4c20fd..9cf7f745 100644 --- a/src/types.ts +++ b/src/types.ts @@ -95,7 +95,7 @@ export interface Options { * Use a <style> in svg to import all styles of current html page, and do not add computed styles to every node any more. * This will make the svg content very small, to resolve problems when html has large amount of sub nodes. * */ - usePageCss?: boolean, + usePageCss?: boolean /* * Check whether the svg tail is integrated. * This will fix some problems that the last page of exported pdf is truncated. diff --git a/src/util.ts b/src/util.ts index bc4c93e4..aa3965d6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -236,8 +236,8 @@ export function svgUrlToImg(urlIn: string, opt: Options = {}) { // 原因诸如:4k屏的1px在html中为0.51px,而在svg中为1px;又如 overflow-y 在svg中失效;background定位不兼容等。 // 为了避免图像底部不完整的情况,这里每次额外增加60px高度,并寻找是否存在底部标志颜色(TailColor),直到已存在,说明已经到达底部。 function checkImg(i: number): Promise<HTMLImageElement> { - let url = replaceHeight(urlIn, TailHeight * i) - return createImage(url).then(function(img) { + const url = replaceHeight(urlIn, TailHeight * i) + return createImage(url).then(function (img) { const prePx = 3 const canvasHeight = (TailHeight * 2) / deviceRatio + prePx const ctx = get2dCtx(1, canvasHeight) @@ -269,7 +269,7 @@ export function svgUrlToImg(urlIn: string, opt: Options = {}) { if (color !== TailColor) { // 分界点位置 const posY = -(canvasHeight - j / 4) * deviceRatio - var url1 = replaceHeight(url, posY) + const url1 = replaceHeight(url, posY) return createImage(url1) } } @@ -281,11 +281,11 @@ export function svgUrlToImg(urlIn: string, opt: Options = {}) { return url .replace( /(viewBox%3D%220%200%20[\d.]+%20)([\d.]+)%22/, - function(_, m1, vpHeight) { + function (_, m1, vpHeight) { return `${m1 + (+vpHeight + delta)}%22` }, ) - .replace(/(%20height%3D%22)([\d.]+)%22/, function(_, m1, height) { + .replace(/(%20height%3D%22)([\d.]+)%22/, function (_, m1, height) { return `${m1 + (+height + delta / deviceRatio)}%22` }) } @@ -310,7 +310,7 @@ export function createImage(url: string): Promise<HTMLImageElement> { export async function svgToDataURL(svg: SVGElement): Promise<string> { return Promise.resolve() - .then(async function() { + .then(async function () { const xml = new XMLSerializer().serializeToString(svg) // open('about:blank').document.write('<plaintext>' + xml); //for debug return xml @@ -360,7 +360,9 @@ export async function nodeToDataURL( if (opt.checkTail) { foreignObject.insertAdjacentHTML( 'beforeend', - `<div style="background: #${TailColor};height:${TailHeight * 2}px"></div>`, + `<div style="background: #${TailColor};height:${ + TailHeight * 2 + }px"></div>`, ) } if (opt.usePageCss) { @@ -372,8 +374,9 @@ export async function nodeToDataURL( return svgToDataURL(svg) } -export const isInstanceOfElement = <T extends typeof Element | typeof HTMLElement | typeof SVGImageElement, - >( +export const isInstanceOfElement = < + T extends typeof Element | typeof HTMLElement | typeof SVGImageElement, +>( node: Element | HTMLElement | SVGImageElement, instance: T, ): node is T['prototype'] => { From e43e2e619292a3c1a6678fc6f592f1c7478bd451 Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Tue, 21 Jan 2025 15:08:45 +0800 Subject: [PATCH 19/21] =?UTF-8?q?Image=E5=B0=9A=E6=9C=AA=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/index.ts | 2 +- src/util.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index db356873..427a13e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-image-big", - "version": "1.11.13", + "version": "1.11.15", "description": "Generates an image from a DOM node using HTML5 canvas and SVG. Support big html page.", "main": "lib/index.js", "module": "es/index.js", diff --git a/src/index.ts b/src/index.ts index dfca4e51..000ef6bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,7 @@ async function prepareNode(node: HTMLElement, options: Options = {}) { if (!('usePageCss' in options)) options.usePageCss = true const clonedNode = (await cloneNode(node, options, true)) as HTMLElement // svg中的图片地址无法离线下载,需要先转成dataUrl - setImgDataUrl(clonedNode) + await setImgDataUrl(clonedNode) return clonedNode } diff --git a/src/util.ts b/src/util.ts index aa3965d6..7f11db6f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -323,6 +323,7 @@ export function setImgDataUrl(el: Element) { const imgs = el.querySelectorAll('img') return Promise.all( [].slice.call(imgs, 0).map((img: HTMLImageElement) => { + if (/^data:/.test(img.src)) return Promise.resolve() return urlToDataUrl(img.src).then((dataUrl) => (img.src = dataUrl)) }), ) From fcf68ab63e5dde8436aca7a13b7a3cd4a55c4f81 Mon Sep 17 00:00:00 2001 From: hzsrc <hz932@qq.com> Date: Thu, 25 Sep 2025 17:56:30 +0800 Subject: [PATCH 20/21] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=B7=A8iframe?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/clone-node.ts | 12 ++--- src/clone-pseudos.ts | 3 +- src/index.ts | 126 ++++++++++++++++++++++++------------------- src/util.ts | 19 ++++--- 4 files changed, 91 insertions(+), 69 deletions(-) diff --git a/src/clone-node.ts b/src/clone-node.ts index 60c27883..5fdc22a5 100644 --- a/src/clone-node.ts +++ b/src/clone-node.ts @@ -1,6 +1,6 @@ import type { Options } from './types' import { clonePseudoElements } from './clone-pseudos' -import { createImage, toArray, isInstanceOfElement } from './util' +import { createImage, toArray, isInstanceOfElement, getDoc } from './util' import { getMimeType } from './mimes' import { resourceToDataURL } from './dataurl' @@ -14,7 +14,7 @@ async function cloneCanvasElement(canvas: HTMLCanvasElement) { async function cloneVideoElement(video: HTMLVideoElement, options: Options) { if (video.currentSrc) { - const canvas = document.createElement('canvas') + const canvas = getDoc().createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = video.clientWidth canvas.height = video.clientHeight @@ -112,7 +112,7 @@ function cloneCSSStyle<T extends HTMLElement>(nativeNode: T, clonedNode: T) { if (!targetStyle) { return } - + const window = nativeNode.ownerDocument.defaultView || self const sourceStyle = window.getComputedStyle(nativeNode) if (sourceStyle.cssText) { targetStyle.cssText = sourceStyle.cssText @@ -200,7 +200,7 @@ async function ensureSVGSymbols<T extends HTMLElement>( const id = use.getAttribute('xlink:href') if (id) { const exist = clone.querySelector(id) - const definition = document.querySelector(id) as HTMLElement + const definition = getDoc().querySelector(id) as HTMLElement if (!exist && definition && !processedDefs[id]) { // eslint-disable-next-line no-await-in-loop processedDefs[id] = (await cloneNode(definition, options, true))! @@ -211,7 +211,7 @@ async function ensureSVGSymbols<T extends HTMLElement>( const nodes = Object.values(processedDefs) if (nodes.length) { const ns = 'http://www.w3.org/1999/xhtml' - const svg = document.createElementNS(ns, 'svg') + const svg = getDoc().createElementNS(ns, 'svg') svg.setAttribute('xmlns', ns) svg.style.position = 'absolute' svg.style.width = '0' @@ -219,7 +219,7 @@ async function ensureSVGSymbols<T extends HTMLElement>( svg.style.overflow = 'hidden' svg.style.display = 'none' - const defs = document.createElementNS(ns, 'defs') + const defs = getDoc().createElementNS(ns, 'defs') svg.appendChild(defs) for (let i = 0; i < nodes.length; i++) { diff --git a/src/clone-pseudos.ts b/src/clone-pseudos.ts index ea1fe656..fcc633d3 100644 --- a/src/clone-pseudos.ts +++ b/src/clone-pseudos.ts @@ -36,6 +36,7 @@ function clonePseudoElement<T extends HTMLElement>( clonedNode: T, pseudo: Pseudo, ) { + const window = nativeNode.ownerDocument.defaultView || self const style = window.getComputedStyle(nativeNode, pseudo) const content = style.getPropertyValue('content') if (content === '' || content === 'none') { @@ -49,7 +50,7 @@ function clonePseudoElement<T extends HTMLElement>( return } - const styleElement = document.createElement('style') + const styleElement = nativeNode.ownerDocument.createElement('style') styleElement.appendChild(getPseudoElementStyle(className, pseudo, style)) clonedNode.appendChild(styleElement) } diff --git a/src/index.ts b/src/index.ts index 000ef6bf..23e030f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import { svgUrlToImg, getStyles, setImgDataUrl, + setDoc, } from './util' export async function toSvg<T extends HTMLElement>( @@ -50,6 +51,7 @@ export async function toImage<T extends HTMLElement>( node: T, options: Options = {}, ): Promise<HTMLImageElement> { + setDoc(node.ownerDocument) const svg = await toSvg(node, options) return svgUrlToImg(svg, options) } @@ -58,74 +60,86 @@ export async function toCanvas<T extends HTMLElement>( node: T, options: Options = {}, ): Promise<HTMLCanvasElement> { - const img = await toImage(node, options) - const { width, height } = getImageSize(node, options, img) - const canvas = document.createElement('canvas') - const context = canvas.getContext('2d', { willReadFrequently: true })! - const ratio = options.pixelRatio || getPixelRatio() - const canvasWidth = options.canvasWidth || width - const canvasHeight = options.canvasHeight || height - - canvas.width = canvasWidth * ratio - canvas.height = canvasHeight * ratio - - if (!options.skipAutoScale) { - checkCanvasDimensions(canvas) - } - canvas.style.width = `${canvasWidth}` - canvas.style.height = `${canvasHeight}` + const doc = node.ownerDocument + setDoc(doc) + try { + const img = await toImage(node, options) + const { width, height } = getImageSize(node, options, img) + const canvas = doc.createElement('canvas') + const context = canvas.getContext('2d', { willReadFrequently: true })! + const ratio = options.pixelRatio || getPixelRatio() + const canvasWidth = options.canvasWidth || width + const canvasHeight = options.canvasHeight || height - if (options.backgroundColor) { - context.fillStyle = options.backgroundColor - context.fillRect(0, 0, canvas.width, canvas.height) - } + canvas.width = canvasWidth * ratio + canvas.height = canvasHeight * ratio + + if (!options.skipAutoScale) { + checkCanvasDimensions(canvas) + } + canvas.style.width = `${canvasWidth}` + canvas.style.height = `${canvasHeight}` - context.drawImage(img, 0, 0, canvas.width, canvas.height) + if (options.backgroundColor) { + context.fillStyle = options.backgroundColor + context.fillRect(0, 0, canvas.width, canvas.height) + } - return canvas + context.drawImage(img, 0, 0, canvas.width, canvas.height) + + return canvas + } finally { + setDoc(document) + } } export async function toCanvasList<T extends HTMLElement>( node: T, options: Options = {}, ): Promise<Array<HTMLCanvasElement>> { - const img = await toImage(node, options) - const { width, height } = getImageSize(node, options, img) - const ratio = options.pixelRatio || getPixelRatio() - let canvasWidth = (options.canvasWidth || width) * ratio - let canvasHeight = (options.canvasHeight || height) * ratio - const dimensionLimit = getDimensionLimit() - if (canvasWidth > dimensionLimit) { - canvasHeight *= dimensionLimit / canvasWidth - canvasWidth = dimensionLimit - } + const doc = node.ownerDocument + setDoc(doc) + try { + const img = await toImage(node, options) + const { width, height } = getImageSize(node, options, img) + const ratio = options.pixelRatio || getPixelRatio() + let canvasWidth = (options.canvasWidth || width) * ratio + let canvasHeight = (options.canvasHeight || height) * ratio + const dimensionLimit = getDimensionLimit() + if (canvasWidth > dimensionLimit) { + canvasHeight *= dimensionLimit / canvasWidth + canvasWidth = dimensionLimit + } - const result: Array<HTMLCanvasElement> = [] - const scale = canvasWidth / img.width - for (let curY = 0; curY < canvasHeight; curY += dimensionLimit) { - const canvas = document.createElement('canvas') - const context = canvas.getContext('2d', { willReadFrequently: true })! - const height1 = Math.min(canvasHeight - curY, dimensionLimit) - canvas.width = canvasWidth - canvas.height = height1 - if (options.backgroundColor) { - context.fillStyle = options.backgroundColor - context.fillRect(0, 0, canvas.width, canvas.height) + const result: Array<HTMLCanvasElement> = [] + const scale = canvasWidth / img.width + for (let curY = 0; curY < canvasHeight; curY += dimensionLimit) { + const canvas = doc.createElement('canvas') + const context = canvas.getContext('2d', { willReadFrequently: true })! + const height1 = Math.min(canvasHeight - curY, dimensionLimit) + canvas.width = canvasWidth + canvas.height = height1 + if (options.backgroundColor) { + context.fillStyle = options.backgroundColor + context.fillRect(0, 0, canvas.width, canvas.height) + } + context.drawImage( + img, + 0, + curY / scale, + canvasWidth / scale, + height1 / scale, + 0, + 0, + canvasWidth, + height1, + ) + result.push(canvas) } - context.drawImage( - img, - 0, - curY / scale, - canvasWidth / scale, - height1 / scale, - 0, - 0, - canvasWidth, - height1, - ) - result.push(canvas) + return result + }finally { + setDoc(document) //避免引用所致内存溢出 } - return result } export async function toPixelData<T extends HTMLElement>( diff --git a/src/util.ts b/src/util.ts index 7f11db6f..9f268b21 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,12 @@ import type { Options } from './types' +let doc : Document = document +export function getDoc() { + return doc +} +export function setDoc(docIn : Document) { + doc = docIn +} export function resolveUrl(url: string, baseUrl: string | null): string { // url is absolute already if (url.match(/^[a-z]+:\/\//i)) { @@ -16,7 +23,7 @@ export function resolveUrl(url: string, baseUrl: string | null): string { return url } - const doc = document.implementation.createHTMLDocument() + const doc = getDoc().implementation.createHTMLDocument() const base = doc.createElement('base') const a = doc.createElement('a') @@ -183,7 +190,7 @@ export function getMaxCanvasHeight(width: number): number { } function get2dCtx(width: number, height: number) { - const canvas = document.createElement('canvas') + const canvas = doc.createElement('canvas') canvas.width = width canvas.height = height return canvas.getContext('2d', { willReadFrequently: true })! @@ -339,8 +346,8 @@ export async function nodeToDataURL( opt: Options = {}, ): Promise<string> { const xmlns = 'http://www.w3.org/2000/svg' - const svg = document.createElementNS(xmlns, 'svg') - const foreignObject = document.createElementNS(xmlns, 'foreignObject') + const svg = doc.createElementNS(xmlns, 'svg') + const foreignObject = doc.createElementNS(xmlns, 'foreignObject') // add a tail for check ending let heightWithTail = height if (opt.checkTail) heightWithTail += TailHeight * 2 @@ -367,7 +374,7 @@ export async function nodeToDataURL( ) } if (opt.usePageCss) { - const style = document.createElementNS(xmlns, 'style') + const style = doc.createElementNS(xmlns, 'style') style.insertAdjacentText('beforeend', await getStyles()) svg.insertBefore(style, foreignObject) } @@ -394,7 +401,7 @@ export const isInstanceOfElement = < } export function getStyles() { - const styles = document.querySelectorAll('style,link[rel="stylesheet"]') + const styles = doc.querySelectorAll('style,link[rel="stylesheet"]') const promises: Array<Promise<string>> = [] toArray(styles).forEach((el) => { const e = el as Element From 4e798a2d0b4d612809a13a379c7df0f64f70f401 Mon Sep 17 00:00:00 2001 From: hzsrc <hz932@qq.com> Date: Thu, 25 Sep 2025 18:32:47 +0800 Subject: [PATCH 21/21] =?UTF-8?q?iframe=E6=94=B9=E7=94=A8=E9=80=92?= =?UTF-8?q?=E5=BD=92=E7=94=9F=E6=88=90=E5=9B=BE=E7=89=87=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E5=86=8Dclone=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/clone-node.ts | 41 +++++++++++++++++++++++++---------------- src/index.ts | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/clone-node.ts b/src/clone-node.ts index 5fdc22a5..e840e44e 100644 --- a/src/clone-node.ts +++ b/src/clone-node.ts @@ -3,6 +3,7 @@ import { clonePseudoElements } from './clone-pseudos' import { createImage, toArray, isInstanceOfElement, getDoc } from './util' import { getMimeType } from './mimes' import { resourceToDataURL } from './dataurl' +import { toDataUrl } from './index' async function cloneCanvasElement(canvas: HTMLCanvasElement) { const dataURL = canvas.toDataURL() @@ -29,21 +30,21 @@ async function cloneVideoElement(video: HTMLVideoElement, options: Options) { return createImage(dataURL) } -async function cloneIFrameElement(iframe: HTMLIFrameElement) { - try { - if (iframe?.contentDocument?.body) { - return (await cloneNode( - iframe.contentDocument.body, - {}, - true, - )) as HTMLBodyElement - } - } catch { - // Failed to clone iframe - } - - return iframe.cloneNode(false) as HTMLIFrameElement -} +// async function cloneIFrameElement(iframe: HTMLIFrameElement) { +// try { +// if (iframe?.contentDocument?.body) { +// return (await cloneNode( +// iframe.contentDocument.body, +// {}, +// true, +// )) as HTMLBodyElement +// } +// } catch { +// // Failed to clone iframe +// } +// +// return iframe.cloneNode(false) as HTMLIFrameElement +// } async function cloneSingleNode<T extends HTMLElement>( node: T, @@ -58,7 +59,15 @@ async function cloneSingleNode<T extends HTMLElement>( } if (isInstanceOfElement(node, HTMLIFrameElement)) { - return cloneIFrameElement(node) + if (node.offsetWidth + node.offsetHeight > 0) { + const body = node?.contentDocument?.body + if(body) { + const img = getDoc().createElement('img') + img.src = await toDataUrl(body, options) + return img + } + } + return getDoc().createElement('div') //cloneIFrameElement(node) } return node.cloneNode(false) as T diff --git a/src/index.ts b/src/index.ts index 23e030f4..d1cbc11c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -141,6 +141,24 @@ export async function toCanvasList<T extends HTMLElement>( setDoc(document) //避免引用所致内存溢出 } } +export async function toDataUrl<T extends HTMLElement>(node: T, options: Options = {}) { + var canvas = await toCanvas(node, options) + return canvas.toDataURL() + //const list = await toCanvasList(node, options) + //if(list.length == 1) return list[0].toDataURL() + // const imgs = list.map(canvas => { + // var img = node.ownerDocument.createElement('img') + // img.src = canvas.toDataURL() + // return new Promise(((resolve, reject) => { + // img.onload = resolve + // img.onerror = reject + // }).then(_=>img) + // }) + // Promise.all(imgs).then(loaded=>{ + // const width = loaded[0].width + // const height = loaded.reduce((c, total) => c + total, 0) + // }) +} export async function toPixelData<T extends HTMLElement>( node: T,