-
Notifications
You must be signed in to change notification settings - Fork 570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is there a way to wait for a few seconds before capturing the canvas to render an image? #369
Comments
Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can. |
https://github.com/qq15725/modern-screenshot/blob/v4.2.12/src/utils.ts#L185-L198 export const isElementNode = (node: Node): node is Element => node.nodeType === 1 // Node.ELEMENT_NODE
export const isSVGElementNode = (node: Element): node is SVGElement => typeof (node as SVGElement).className === 'object'
export const isSVGImageElementNode = (node: Element): node is SVGImageElement => isSVGElementNode(node) && node.tagName === 'IMAGE'
export const isHTMLElementNode = (node: Node): node is HTMLElement => isElementNode(node) && typeof (node as HTMLElement).style !== 'undefined' && !isSVGElementNode(node)
export const isImageElement = (node: Element): node is HTMLImageElement => node.tagName === 'IMG'
export const isVideoElement = (node: Element): node is HTMLVideoElement => node.tagName === 'VIDEO'
export const consoleWarn = (...args: any[]) => console.warn(...args)
export function getDocument<T extends Node>(target?: T | null): Document {
return (
(
target && isElementNode(target as any)
? target?.ownerDocument
: target
) ?? window.document
) as any
}
export function createImage(url: string, ownerDocument?: Document | null, useCORS = false): HTMLImageElement {
const img = getDocument(ownerDocument).createElement('img')
if (useCORS) {
img.crossOrigin = 'anonymous'
}
img.decoding = 'sync'
img.loading = 'eager'
img.src = url
return img
}
type Media = HTMLVideoElement | HTMLImageElement | SVGImageElement
interface LoadMediaOptions {
ownerDocument?: Document
timeout?: number
}
export function loadMedia<T extends Media>(media: T, options?: LoadMediaOptions): Promise<T>
export function loadMedia(media: string, options?: LoadMediaOptions): Promise<HTMLImageElement>
export function loadMedia(media: any, options?: LoadMediaOptions): Promise<any> {
return new Promise(resolve => {
const { timeout, ownerDocument } = options ?? {}
const node: Media = typeof media === 'string'
? createImage(media, getDocument(ownerDocument))
: media
let timer: any = null
let removeEventListeners: null | (() => void) = null
function onResolve() {
resolve(node)
timer && clearTimeout(timer)
removeEventListeners?.()
}
if (timeout) {
timer = setTimeout(onResolve, timeout)
}
if (isVideoElement(node)) {
const poster = node.poster
if (poster) {
return loadMedia(poster, options).then(resolve)
}
const currentSrc = (node.currentSrc || node.src)
if (node.readyState >= 2 || !currentSrc) {
return onResolve()
}
const onLoadeddata = onResolve
const onError = (error: any) => {
consoleWarn(
'Video load failed',
currentSrc,
error,
)
onResolve()
}
removeEventListeners = () => {
node.removeEventListener('loadeddata', onLoadeddata)
node.removeEventListener('error', onError)
}
node.addEventListener('loadeddata', onLoadeddata, { once: true })
node.addEventListener('error', onError, { once: true })
} else {
const currentSrc = isSVGImageElementNode(node)
? node.href.baseVal
: (node.currentSrc || node.src)
if (!currentSrc) {
return onResolve()
}
const onLoad = async () => {
if (isImageElement(node) && 'decode' in node) {
try {
await node.decode()
} catch (error) {
consoleWarn(
'Failed to decode image, trying to render anyway',
node.dataset.originalSrc || currentSrc,
error,
)
}
}
onResolve()
}
const onError = (error: any) => {
consoleWarn(
'Image load failed',
node.dataset.originalSrc || currentSrc,
error,
)
onResolve()
}
if (isImageElement(node) && node.complete) {
return onLoad()
}
removeEventListeners = () => {
node.removeEventListener('load', onLoad)
node.removeEventListener('error', onError)
}
node.addEventListener('load', onLoad, { once: true })
node.addEventListener('error', onError, { once: true })
}
})
}
export async function waitUntilLoad(node: Node, timeout: number) {
if (isHTMLElementNode(node)) {
if (isImageElement(node) || isVideoElement(node)) {
await loadMedia(node, { timeout })
} else {
await Promise.all(
['img', 'video'].flatMap(selectors => {
return Array.from(node.querySelectorAll(selectors))
.map(el => loadMedia(el as any, { timeout }))
}),
)
}
}
} |
Some libs use promises, html-to-image for example. It runs in the background. Safari, perhaps in the name of performance, ignores these promises and the HTML is converted to PNG without the images included in the html block. We found the solution here: Also avoid using .then and .catch in functions, opt for async functions with await. |
General Summary of the Problem
The canvas captures the html node before it's completely rendered on the canvas. I'm just assuming that this is the case because when:
Here's a visual of those three cases I mentioned:
In other words, I'm assuming that the problem is because the
<img />
elements are not completely loaded into the canvas yet but thehtml-to-image
library captures it and renders it right away.Expected Behavior
The expected behavior should be rendering the 3rd image right away.
Current Behavior
(already explained this in the general summary)
This bug also doesn't happen on the computer/laptop but happens on my phone for some reason. I'm just assuming that the computer has more computing resources than a phone.
Possible Solution
I think the easiest fix for this when working with
<img />
that take time to load, is to just simply delay the "capture" of the image on the canvas.So is there a way to delay the "capture" of the image while it gets rendered on the canvas?
Steps To Reproduce
Can't really give steps to reproduce but try using an element with a large image as a node and then use
downloadjs
with that and try downloading on a phone.The text was updated successfully, but these errors were encountered: