Suggestion for blank image on safari
The lib works great on chrome, but broke on safari. After several tries, I have summarized the following points:
- images of svg type are not supported on safari
- cross-origin images are not supported
- first call to toBlob/toCanvas/toPng... always render blank images, call the method twice
Hope those can save your day!
Potential duplicates:
- [#461] Blank Image in Safari (70%)
👋 @joe06102
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 have experienced a similar issue where the returned image is empty. This has been traced to some specific IOS devices when we exceed the max canvas size supported by the browser/device. You might be able to gain additional information using the npm package 'canvas-size'.
Currently I do not have a good solution manipulating the height and canvasHeight options to be within the bounds of the canvas limitations
This seems to have been fixed with 1.11.12 or 1.11.13, but bun wasn't seeing that update, so I manually updated and I think the problem is gone for me!
Still doesn't work on Safari 18.4 (iPad) with v1.11.13. I tried all the workarounds I could find here and the only way I got this to work on iPad Safari was via modern-screenshot fork of html-to-image.
This library works like a charm on all other devices and on any Chromium / Firefox browser derivative I tested, but the issue seems to persist on iOS.
same issue here
For anyone who's affected by this issue, here's a workaround that seems to be a lot more reliable than the fixes in recent 1.11.12 or 1.11.13 versions, and more reliable than the suggestion in another Issue to await a 1 second timeout:
To demonstrate this fix, I'll start by referring to this library's documentation:
htmlToImage
.toPng(document.getElementById('my-node'))
.then((dataUrl) => download(dataUrl, 'my-node.png'));
The problem is in Safari, not everything may be rendered in the png, when the promise is resolved and the .then((dataUrl) => ...) is invoked.
If you experience this issue where not everything is rendered, in particular a canvas is blank, run that same function one more time:
htmlToImage
.toPng(document.getElementById('my-node'))
.then((dataUrl) => download(dataUrl, 'my-node.png'));
You will notice this time the dataUrl is now longer. This is because the the dataUrl encodes the snapshotted image, and Safari has now rendered more than last time. The length of the dataUrl provides some information about how much was rendered.
The solution then, is to recursively call this function until dataUrl.length stops increasing, suggesting Safari is done rendering.
htmlToImage
.toPng(document.getElementById('my-node'))
So the final solution could look something like this:
const captureSnapshot = async (previousDataUrl = "") => {
const dataUrl = await htmlToImage.toPng(document.getElementById('my-node'))
console.log("DEBUG dataUrl.length: ", dataUrl.length)
return (dataUrl.length !== previousDataUrl.length)
? captureSnapshot(dataUrl)
: dataUrl
}
const dataUrl = await captureSnapshot()
download(dataUrl, 'my-node.png')
The downside is that htmlToImage.toPng() would always run at least twice, but for the gain in reliability, I think this workaround is worth it.
Verified for:
- Safari Version 17.1 (19616.2.9.11.7) on MacOS 14.1.1 (23B81), Apple M1 Pro
- Safari on iOS 17.6.1, iPhone 15 Pro Max
- Safari on iOS 16.4.1 (a), iPad Pro (12.9-inch) (6th generation)
@MFinn87 for me the workaround doesn't always work after 2 attempts.
// Known issue with html-to-image: https://github.com/bubkoo/html-to-image/issues/488
let dataUrl = ''
let attempts = 0
const MIN_LENGTH = 5000000
while (dataUrl.length < MIN_LENGTH) {
if (attempts > 10) {
throw new Error('Failed to save image')
}
attempts++
dataUrl = await toPng(document.getElementById('my-node'))
await new Promise(resolve => setTimeout(resolve, 1000))
}
EDIT: although at least 2 consecutive generations are needed for comparison, MFinn87's approach is better
@mickaelchanrion :-/ I'm sorry to read that.
There are a ton of variables that might play into why you're still seeing the issue (we're obviously not snapshotting the same webpage or components, I'm not even sure what version of Safari you're using, maybe SSR plays into this (I'm only doing CSR)). There are also a few differences between the behavior of your code, and my code in my point 5, but whether those differences matter or not is hard to say.
If you can post a minimally reproducible example of still being affected on Codepen (or something similar), I can try and find time to troubleshoot for you, no guarantees. This isn't my project and clearly the root cause is buried within Safari.
If not, 2 other workarounds I can think of are:
- If you can generate the screenshot on the server side, I do know that Playwright takes very reliable screenshots: https://playwright.dev/docs/intro This might be a heavy handed approach though, since it's really more of a testing & automation framework and basically runs headless Chrome under the hood. Since Chrome would be run on the server, the user's browser won't matter at all.
- Someone else suggested this library instead: https://github.com/qq15725/modern-screenshot
@MFinn87 oops, sorry I read your code too fast. I thought you were generating only 2 times and didn't see it was recursive. That's why I posted an alternative code. You approach is actually better since mine implies an arbitrary value to consider if the generation is valid. Thank you for sharing this workaround 😃
This workaround patch fixed it for me: https://github.com/BricksInc/html-to-image/commit/1eb95586dda67dfc94e7d3bd1f74615301d1f023
@mickaelchanrion :-/ I'm sorry to read that.
There are a ton of variables that might play into why you're still seeing the issue (we're obviously not snapshotting the same webpage or components, I'm not even sure what version of Safari you're using, maybe SSR plays into this (I'm only doing CSR)). There are also a few differences between the behavior of your code, and my code in my point 5, but whether those differences matter or not is hard to say.
If you can post a minimally reproducible example of still being affected on Codepen (or something similar), I can try and find time to troubleshoot for you, no guarantees. This isn't my project and clearly the root cause is buried within Safari.
If not, 2 other workarounds I can think of are:
- If you can generate the screenshot on the server side, I do know that Playwright takes very reliable screenshots: https://playwright.dev/docs/intro This might be a heavy handed approach though, since it's really more of a testing & automation framework and basically runs headless Chrome under the hood. Since Chrome would be run on the server, the user's browser won't matter at all.
- Someone else suggested this library instead: https://github.com/qq15725/modern-screenshot
The suggested library here worked for me ✅