-
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
Images are sometimes skipped via iOS #420
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. |
Hi @getCryptoAddress, have you been able to move past this yet? |
Having the same issues here... I can reproduce this issue even on v1.11.0 |
Same here |
This is an iOS issue unfortunately https://bugs.webkit.org/show_bug.cgi?id=219770 I partially resolved this adding a delay of 250ms after the creation of the svg and before drawing to the canvas. 250 is just a magic number that I know in most of times image has been loaded. For images of high size and if you haven’t loaded it into the dom probably it’s not enough toCanvas()
|
Same here |
Finally found a reliable solution: the modern-screenshot library (a fork of html-to-image). Completely fixed the problem for me! H/t @qq15725 |
beware that this solution seems still clone image like everyone else (https://github.com/qq15725/modern-screenshot/blob/main/src/clone-image.ts), so probably it is fixed by implicitly being asynchronous and adding some delays, since it uses worker (I don't know if it's a good idea, since the major computation only concerns the dom which is not available in the worker side) default |
He explained the difference here: What do you think? |
Delaying the final canvas rendering by a fixed timeout can be the solution for most of cases. It’s valid in my opinion since there isn’t any way to know if safari will render the image. What I don’t like it’s this magic number which doesn’t guarantee it works every time 😅, but probably it’s the best it can be done at the moment. Anyway this solution seems delaying n seconds for each image present in the html structure, so if you have like 10 images, it will be delayed for 1000ms~ (10 * 100ms) 🤔 but I don’t have the full understand of the code so I may be wrong. Another important thing: what’s the device we are using to render that image, and how much mb is it? Assuming we are trying to render an image around 0-1mb, most of iOS and macOS device can render it with a timeout of 100. If the image gets bigger, we surely need to increment this timeout. But I think there are others factors like device speed (what if energy save mode is on?) currently I think the most safest way of rendering an image without having issues is using puppeteer/playwright with chrome under the hood 🤷🏻♂️ doing this will also allows to export the same image for all sort of devices (e.g. safari still can’t handle box-shadow), but it may be expensive |
10 image ref = 10 (canvasContext2d.drawImage('data:image/svg+xml,html-to-svg-data') and await 100ms) I don't know how it works, but it works 😄 |
Can confirm that it's working beautifully! Thanks again @qq15725 :) (For me, it's working on photos >5MB... will post an update if it stops working but for now I think we're good to go) |
Thanks, work for me too. |
await new Promise((resolve) => setTimeout(resolve, 1)); ![]()
__ UPDATED__ __ UPDATED__ |
Fix bubkoo#420
Vue code example <script lang="ts" setup>
// ....
// get svg from "html-to-image" for html
canvasSvgElement.value = decodeURIComponent(await toSvg(targetElement));
// wait vue render
await nextTick();
// get images into svg
const images = canvasImageWrapperElement.value?.querySelectorAll("img");
// wait all images
if (images) {
await Promise.all(
[...images].map((img) => {
return new Promise((resolve, reject) => {
if (img.complete) {
resolve(null);
} else {
img.onload = resolve;
img.onerror = reject;
}
});
})
);
console.log("images loaded"); // <--- it's true for safari How I understand, safari needs time to render, not just load |
|
Hi everyone, and thanks, @vicecitydeluxe, for the solution! 👍 I tried removing all the workarounds locally and tested with Unfortunately, it’s still not a complete fix, so I had to bring back the workarounds. But it’s a solid addition and works well with: import { toCanvas } from "html-to-image";
export default async function convertHtmlToCanvas(
targetElement: HTMLElement,
countReRender: number = 0,
timeout: number = 1,
): Promise<HTMLCanvasElement> {
const canvas = await toCanvas(targetElement);
await new Promise((resolve) => setTimeout(resolve, timeout)); // ← time to render
if (countReRender > 0) { // ↓ Let's try to redo it recursively N times
return convertHtmlToCanvas(targetElement, countReRender - 1, timeout);
}
return canvas;
} How to use: const canvas = isSafariOrIos()
? await convertHtmlToCanvas(targetElement, 10, 150) // safari loves pain
: await convertHtmlToCanvas(targetElement); |
Still dealing with this. |
Expected Behavior
Images always render
Current Behavior
Images are sometimes skipped via iOS
For example: The background appeared only for the 3rd time

Possible Solution
Steps To Reproduce
Error Message & Stack Trace
no errors
Additional Context
Code:
Your Environment
The text was updated successfully, but these errors were encountered: