Blank Image in Safari
We are basically trying to take screenshots of a div which has images as it's child the output we are getting has blank spaces instead of images this works fine on chrome but safari has this issue
👋 @himanshupatil363
Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. To help make it easier for us to investigate your issue, please follow the contributing guidelines.
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.
I also encountered this problem
It seems to be a Safari bug that doesn't load the canvas immediately when the web page is rendered.
I tried to fork the code and fix it by setting a timeout when loading an image in util.ts within the createImage function.
from
img.onload = () => resolve(img)
to
img.onload = () => {
setTimeout(function () {
resolve(img);
}, 300);
};
img.onload = () => { setTimeout(function () { resolve(img); }, 300); };
this works in mac safari but doesnt work in ios safari tho after 2-3 times the img does show
img.onload = () => { setTimeout(function () { resolve(img); }, 300); };
this works in mac safari but doesnt work in ios safari tho after 2-3 times the img does show
Another way you can try is to loop the toCanvas function, but it's not the best practice because you don't know how many times to show the image.
export async function convertHtmlToCanvas(
targetElement: HTMLElement,
countReRender: number = 0,
): Promise<HTMLCanvasElement> {
let canvas: any;
for (let i = 0; i < countReRender; i++) {
canvas = await toCanvas(targetElement);
}
return canvas;
}
In the same situation, the problems encountered in Safari are a headache,
¿Has anyone found a better solution than the loop solution?
Here's how I fixed mine in Safari. I added delay using modern-screenshot: https://github.com/qq15725/modern-screenshot/tree/main
Which also came from html-to-image.
`` if (!isSafari) { dataURL = await htmlToImage.toPng(imageElement); } else { //put a timer on the domToCanvas for 500ms to make sure the image is loaded dataURL = await domToCanvas(imageElement, { delay: 500, debug: true, }).then((canvas) => { dataURL = canvas.toDataURL("image/png"); return dataURL; }); }
@DMTaswebdevelopment There is no delay option.
I used patch-package to patch [email protected] for the project I'm working on.
Here is the diff that solved my problem:
diff --git a/node_modules/html-to-image/es/index.js b/node_modules/html-to-image/es/index.js
index 0eda938..768f239 100644
--- a/node_modules/html-to-image/es/index.js
+++ b/node_modules/html-to-image/es/index.js
@@ -16,6 +16,10 @@ export async function toCanvas(node, options = {}) {
const { width, height } = getImageSize(node, options);
const svg = await toSvg(node, options);
const img = await createImage(svg);
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
+ if (isSafari) {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ }
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const ratio = options.pixelRatio || getPixelRatio();
@RonaldR you are a life saver man, i also seen this issue in any ios or mac os device even if chrome is used so i made mine more inclusive :
const isSafariOrApple = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) || /iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent); if (isSafariOrApple) { await new Promise(resolve => setTimeout(resolve, 1000)); }
FYI: 1.11.12 or 1.11.13 seems to have fixed this!
I'm using 1.11.13 but still doesn't workk.
I'm using 1.11.13 but still doesn't workk.
me too
Me too, it doesnt work on 1.11.13, still blank image
any update here?
I can confirm it is still an issue. Safari 18.5, Mac mini M2. Images are blank. They render usually in third call of htmlToImage.
Edit: I have the images in base64.
I used patch-package to patch
[email protected]for the project I'm working on.Here is the diff that solved my problem:
diff --git a/node_modules/html-to-image/es/index.js b/node_modules/html-to-image/es/index.js index 0eda938..768f239 100644 --- a/node_modules/html-to-image/es/index.js +++ b/node_modules/html-to-image/es/index.js @@ -16,6 +16,10 @@ export async function toCanvas(node, options = {}) { const { width, height } = getImageSize(node, options); const svg = await toSvg(node, options); const img = await createImage(svg);
- const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
- if (isSafari) {
await new Promise(resolve => setTimeout(resolve, 1000));- } const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); const ratio = options.pixelRatio || getPixelRatio();
greate job!
Found an extra problem : image can not too big such as 1024x1024, work well with 128 x 128
the latest update
found need delay 1s each image, which mean 2s if 2 images, 3s if 3 images
This is still present, and should be reopen. on version .13 on safari it still renders blank images. sometimes it works, some other times it doesnt.
the patch patching with manual delay also is not realiable
Have someone tested the new Safari 26?
In case anyone is still having issues after patching or any other solution:
We also ran into the behavior described in the “Safari doesn’t load the canvas immediately” note. Instead of patching html-to-image, we worked around it by detecting Mobile Safari and invoking the conversion twice—once as a no-op warm-up, then once to get the actual data. That second call consistently returns the populated canvas, while other browsers just execute the single call you’d expect.
const isMobileSafari = () => {
if (typeof navigator === 'undefined') return false
return (
/Safari/i.test(navigator.userAgent) &&
!/Chrome|CriOS|FxiOS|OPiOS|EdgiOS/i.test(navigator.userAgent) &&
/Mobile|iP(ad|hone|od)/i.test(navigator.userAgent)
)
}
const createExportOptions = () => ({
cacheBust: true,
filter: (element: Element) => !element?.classList?.contains('print:hidden'),
})
const EXPORT_STRATEGIES = {
download: async (node: HTMLElement, filename = 'devpunks-avatar.png') => {
const options = createExportOptions()
if (isMobileSafari()) await toPng(node, options) // warm-up
const dataUrl = await toPng(node, options)
const link = document.createElement('a')
link.download = filename
link.href = dataUrl
link.click()
},
share: async (node: HTMLElement, filename = 'devpunks-avatar.png') => {
const options = createExportOptions()
if (isMobileSafari()) await toBlob(node, options) // warm-up
const blob = await toBlob(node, options)
if (!blob) throw new Error('Failed to convert DOM node to PNG blob')
const file = new File([blob], filename, { type: 'image/png' })
if (navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({
title: 'Devpunks Avatar',
text: 'Check out my Devpunks avatar!',
files: [file],
})
} else {
await navigator.share({
title: 'Devpunks Avatar',
text: 'Check out my Devpunks avatar!',
url: window.location.href,
})
}
},
}
Not elegant, but it avoided and behaved better than patch-package and reliably unblocked Mobile Safari for us.
maybe just try https://github.com/qq15725/modern-screenshot , works perfect