html-to-image icon indicating copy to clipboard operation
html-to-image copied to clipboard

Blank Image in Safari

Open himanshupatil363 opened this issue 1 year ago • 10 comments

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 avatar Apr 12 '24 23:04 himanshupatil363

👋 @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.

biiibooo[bot] avatar Apr 12 '24 23:04 biiibooo[bot]

I also encountered this problem

JokeShaco avatar Apr 18 '24 09:04 JokeShaco

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);
    };

frong123nk avatar May 30 '24 19:05 frong123nk

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

jagabs avatar Jun 08 '24 11:06 jagabs

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;
}

frong123nk avatar Jun 10 '24 02:06 frong123nk

In the same situation, the problems encountered in Safari are a headache,

Rabithua avatar Jun 14 '24 09:06 Rabithua

¿Has anyone found a better solution than the loop solution?

Sebas3245 avatar Jun 27 '24 15:06 Sebas3245

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 avatar Jul 03 '24 02:07 DMTaswebdevelopment

@DMTaswebdevelopment There is no delay option.

nyeoni avatar Aug 04 '24 08:08 nyeoni

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 avatar Sep 03 '24 14:09 RonaldR

@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)); }

BHK0 avatar Dec 25 '24 23:12 BHK0

FYI: 1.11.12 or 1.11.13 seems to have fixed this!

theDanielJLewis avatar Apr 23 '25 16:04 theDanielJLewis

I'm using 1.11.13 but still doesn't workk.

song-jinkyu avatar Apr 25 '25 05:04 song-jinkyu

I'm using 1.11.13 but still doesn't workk.

me too

Rabithua avatar Apr 25 '25 06:04 Rabithua

Me too, it doesnt work on 1.11.13, still blank image

eduardcotmrf avatar May 27 '25 07:05 eduardcotmrf

any update here?

KingAmo avatar Jun 10 '25 12:06 KingAmo

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.

FilipChalupa avatar Jun 18 '25 13:06 FilipChalupa

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

krmao avatar Jul 15 '25 06:07 krmao

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

stefan-garofalo avatar Sep 24 '25 11:09 stefan-garofalo

Have someone tested the new Safari 26?

FilipChalupa avatar Sep 24 '25 11:09 FilipChalupa

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.

stefan-garofalo avatar Sep 24 '25 13:09 stefan-garofalo

maybe just try https://github.com/qq15725/modern-screenshot , works perfect

krmao avatar Sep 26 '25 01:09 krmao