Skip to content
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

Image is not showing in some cases iOS, Safari #361

Open
asaleh267 opened this issue Jan 9, 2023 · 23 comments
Open

Image is not showing in some cases iOS, Safari #361

asaleh267 opened this issue Jan 9, 2023 · 23 comments

Comments

@asaleh267
Copy link

The html is converted to png without the images included in the html block.
It shows white background replaced instead of the images

It happens sometimes not everytime, specially on iOS, Safari devices

@vivcat
Copy link
Contributor

vivcat bot commented Jan 9, 2023

Potential duplicates:

@vivcat
Copy link
Contributor

vivcat bot commented Jan 9, 2023

👋 @asaleh267

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.

@andreemic
Copy link

steps to reproduce:
try to render an element which includes an image tag like

<img src="https://via.placeholder.com/150"/>

sometimes it has enough time to load the image, sometimes it doesn't

@GMaiolo
Copy link

GMaiolo commented Jan 24, 2023

@asaleh267 a quick workaround is to execute the function several times (2 or 3 times) like this:

await toPng(...);
await toPng(...);
await toPng(...);

const result = await toPng(...); // This should have the images properly loaded

@qq15725
Copy link

qq15725 commented Feb 2, 2023

@asaleh267 this problem seems to be a bug of Safari, when drawing svg+xml, some images are not decoded

A temporary fix

https://github.com/qq15725/modern-screenshot/blob/v4.2.12/src/converts/image-to-canvas.ts#L29-L39

Example code:

const loadedImageCounts = IS_SAFARI ? (context.images.size || 1) : 1
for (let i = 0; i < loadedImageCounts; i++) {
  await new Promise<void>(resolve => {
    setTimeout(() => {
      try {
        context?.drawImage(loaded, 0, 0, canvas.width, canvas.height)
      } catch (error) {
        console.warn('Failed to image to canvas', error)
      }
      resolve()
    }, 100 + i)
  })
}

@Icegreeen
Copy link

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.

Also avoid using .then and .catch in functions, opt for async functions with await.
It may take a few seconds, so just add a toast to let the user know something is up.

The solution below solved my problem:

@asaleh267 a quick patch is to execute the function several times (2 or 3 times) like this:

await toPng(...);
await toPng(...);
await toPng(...);

const result = await toPng(...); // This should have the images properly loaded

@Lucas-lululu
Copy link

@asaleh267 a quick patch is to execute the function several times (2 or 3 times) like this:

await toPng(...);
await toPng(...);
await toPng(...);

const result = await toPng(...); // This should have the images properly loaded

This still doesn't solve the problem, I increase the number of calls to 5 times, but the picture still can't be loaded

@GMaiolo
Copy link

GMaiolo commented Mar 5, 2023

@Lucas-lululu is the image very big? I assume it may be related

@jdmcleod
Copy link

jdmcleod commented Mar 8, 2023

@Lucas-lululu is the image very big? I assume it may be related

The workaround also is not working for me, and I don't think image size affects it.

@acartmell
Copy link

acartmell commented Apr 12, 2023

Instead of trying to guess the right number of times to call await toPng(...), here's a workaround that should hopefully work more consistently. All you need to do is adjust the value for minDataLength based on the image you're generating. You can determine that by logging console.log(dataUrl.length) in a browser that doesn't have this issue, like Chrome.

  const buildPng = async () => {
    const element = document.getElementById('image-node');

    let dataUrl = '';
    const minDataLength = 2000000;
    let i = 0;
    const maxAttempts = 10;

    while (dataUrl.length < minDataLength && i < maxAttempts) {
      dataUrl = await toPng(element);
      i += 1;
    }

    return dataUrl;
  };

This worked for me when working with large assets that took multiple attempts until the call worked.

@wenlittleoil
Copy link

@asaleh267 a quick patch is to execute the function several times (2 or 3 times) like this:

await toPng(...);
await toPng(...);
await toPng(...);

const result = await toPng(...); // This should have the images properly loaded

I don't think it's a good way to solve this problem.

@GMaiolo
Copy link

GMaiolo commented Apr 24, 2023

@asaleh267 a quick patch is to execute the function several times (2 or 3 times) like this:

await toPng(...);
await toPng(...);
await toPng(...);

const result = await toPng(...); // This should have the images properly loaded

I don't think it's a good way to solve this problem.

It's not. It's a workaround.

@Lucas-lululu
Copy link

@Lucas-lululu is the image very big? I assume it may be related

I found that when I generated pictures, I would introduce a lot of fonts, so I disabled them all, and the pictures came out soon
image

@petermarkovich
Copy link

i have similar problem on the ios devices. but with toSvg - work ok and image always shown in the result

@natBizitza
Copy link

Instead of trying to guess the right number of times to call await toPng(...), here's a workaround that should hopefully work more consistently. All you need to do is adjust the value for minDataLength based on the image you're generating. You can determine that by logging console.log(dataUrl.length) in a browser that doesn't have this issue, like Chrome.

  const buildPng = async () => {
    const element = document.getElementById('image-node');

    let dataUrl = '';
    const minDataLength = 2000000;
    let i = 0;
    const maxAttempts = 10;

    while (dataUrl.length < minDataLength && i < maxAttempts) {
      dataUrl = await toPng(element);
      i += 1;
    }

    return dataUrl;
  };

This worked for me when working with large assets that took multiple attempts until the call worked.

This is the only thing that worked for me in Safari. Thank you! However, I would like to have a more clear understanding of why it's happening.

@jonathanrstern
Copy link

This works with small images - but still having trouble with larger ones.

Has anyone figured out a workaround?

html2canvas works perfectly, but it's a little slower so I was really hoping to get html-to-image to work!

@Matt-Jensen
Copy link

This works with small images - but still having trouble with larger ones.

Has anyone figured out a workaround?

html2canvas works perfectly, but it's a little slower so I was really hoping to get html-to-image to work!

Firstly: lol at this workaround. Can't believe it works.
Secondly: Larger images do tend to fail because only part of the image will render, but will still meet the minDataLength threshold. There's a smart solution to this problem (ie estimating a realistic byte size as the threshold), but ultimately I found generating the image about 4 times works 🤷‍♂️

@pgYou
Copy link

pgYou commented Jan 21, 2024

Instead of trying to guess the right number of times to call await toPng(...), here's a workaround that should hopefully work more consistently. All you need to do is adjust the value for minDataLength based on the image you're generating. You can determine that by logging console.log(dataUrl.length) in a browser that doesn't have this issue, like Chrome.

  const buildPng = async () => {
    const element = document.getElementById('image-node');

    let dataUrl = '';
    const minDataLength = 2000000;
    let i = 0;
    const maxAttempts = 10;

    while (dataUrl.length < minDataLength && i < maxAttempts) {
      dataUrl = await toPng(element);
      i += 1;
    }

    return dataUrl;
  };

This worked for me when working with large assets that took multiple attempts until the call worked.

right
mabey await some times, then try again is better.

const buildPng = async (node: HTMLElement) => {
  let dataUrl = ''
  const minDataLength = 2000000
  let i = 0
  const maxAttempts = 10
  dataUrl = await toPng(node)
  while (dataUrl.length < minDataLength && i < maxAttempts) {
    await new Promise((resolve) => {
      setTimeout(() => resolve(null), 300)
    })
    dataUrl = await toPng(node)
    i += 1
  }

  return dataUrl
}

@rogerkerse
Copy link

None of those variants work, if you have multiple different images on your element. they get replaces all with the same 1 image instead

@liamcharmer
Copy link

Any solid solutions?

@spidercodeur
Copy link

spidercodeur commented May 20, 2024

To optimize the only current solution, I did this.
By checking the sizes, there is only one change, when it is detected, we stop the loop.
without enlargement it passes in 2 or 3 cycles

const buildPng = async () => {
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    let dataUrl = '';
    let i = 0;
    let maxAttempts;
    if (isSafari) {
      maxAttempts = 5;
    } else {
      maxAttempts = 1;
    }
    let cycle = [];
    let repeat = true;

    while (repeat && i < maxAttempts) {
      dataUrl = await toPng(contentToPrint.current as HTMLDivElement, {
        fetchRequestInit: {
          cache: 'no-cache',
        },
        skipAutoScale: true,
        includeQueryParams: true,

        pixelRatio: isSafari ? 1 : 3,
        quality: 1,
        filter: filter,
        style: { paddingBottom: '100px' },
      });
      i += 1;
      cycle[i] = dataUrl.length;

      if (dataUrl.length > cycle[i - 1]) repeat = false;
    }
    //console.log('safari:' + isSafari + '_repeat_need_' + i);
    return dataUrl;
};

@Adam-Greenan
Copy link

To optimize the only current solution, I did this. By checking the sizes, there is only one change, when it is detected, we stop the loop. without enlargement it passes in 2 or 3 cycles

const buildPng = async () => {
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    let dataUrl = '';
    let i = 0;
    let maxAttempts;
    if (isSafari) {
      maxAttempts = 5;
    } else {
      maxAttempts = 1;
    }
    let cycle = [];
    let repeat = true;

    while (repeat && i < maxAttempts) {
      dataUrl = await toPng(contentToPrint.current as HTMLDivElement, {
        fetchRequestInit: {
          cache: 'no-cache',
        },
        skipAutoScale: true,
        includeQueryParams: true,

        pixelRatio: isSafari ? 1 : 3,
        quality: 1,
        filter: filter,
        style: { paddingBottom: '100px' },
      });
      i += 1;
      cycle[i] = dataUrl.length;

      if (dataUrl.length > cycle[i - 1]) repeat = false;
    }
    //console.log('safari:' + isSafari + '_repeat_need_' + i);
    return dataUrl;
};

This worked brilliantly for us.

We had this issue when taking canvas images with any of the screenshot libraries including Modern Screenshot.

Our issues were not specific to Safari, it was more towards any browser that was on iOS, Safari or Chromium.

const createCanvas = async (node: HTMLImageElement) => {
  const isSafariOrChrome = /safari|chrome/i.test(navigator.userAgent) && !/android/i.test(navigator.userAgent);

  let dataUrl = "";
  let canvas;
  let i = 0;
  let maxAttempts;
  if (isSafariOrChrome) {
    maxAttempts = 5;
  } else {
    maxAttempts = 1;
  }
  let cycle = [];
  let repeat = true;

  while (repeat && i < maxAttempts) {
    canvas = await htmlToImage.toCanvas(node as HTMLImageElement, {
      fetchRequestInit: {
        cache: "no-cache",
      },
      skipFonts: true,
      includeQueryParams: true,
      quality: 1,
    });
    i += 1;
    dataUrl = canvas.toDataURL("image/png");
    cycle[i] = dataUrl.length;

    if (dataUrl.length > cycle[i - 1]) repeat = false;
  }
  console.log("is safari or chrome:" + isSafariOrChrome + "_repeat_need_" + i);
  return canvas;
};

@vicecitydeluxe
Copy link

#420
see my comment down below in the same issue, hope it will help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests