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 often missing on first render on Safari iOS #343

Open
DanielStout5 opened this issue Jun 25, 2020 · 40 comments
Open

Image is often missing on first render on Safari iOS #343

DanielStout5 opened this issue Jun 25, 2020 · 40 comments

Comments

@DanielStout5
Copy link

DanielStout5 commented Jun 25, 2020

I'm using domtoimage.toPng(element) where element contains a few different elements, including a .png img that has partial transparency.

That img is missing from the resulting data URL on the first call on iPhones. Subsequent calls do include the img. There are two other img inside element that always show up in every toPng result, including the first one. Those other .pngs have transparency as well, but it isn't partial - they are either fully opaque text on a fully transparent background.

@DanielStout5 DanielStout5 changed the title Image is missing on first render on Safari iOS Image is often missing on first render on Safari iOS Jun 25, 2020
@cloydlau
Copy link

same problem!

@DanielStout5
Copy link
Author

My fix was to stop using domtoimage and use html2canvas instead.

@sombek
Copy link

sombek commented Jul 1, 2020

It was working fine with me on chrome, but on iphone usually it miss some pictures.
I changed domtoimage.toJpeg(element) to domtoimage.toPng(child)
I tested it on multiple mobiles, it seems working fine.

Try it from your side and let me know how it goes with you 👍

domtoimage.toPng(element)
const doc = new jsPDF("p", "mm", "a4");
doc.addImage(page, 'JPEG', 0, 0, 210, 297);

@sombek
Copy link

sombek commented Jul 2, 2020

It was working fine with me on chrome, but on iphone usually it miss some pictures.
I changed domtoimage.toJpeg(element) to domtoimage.toPng(child)
I tested it on multiple mobiles, it seems working fine.

Try it from your side and let me know how it goes with you 👍

domtoimage.toPng(element)
const doc = new jsPDF("p", "mm", "a4");
doc.addImage(page, 'JPEG', 0, 0, 210, 297);

Update:
It is still there 🙃
I am planning to do something I will update you in couple of hours

@sombek
Copy link

sombek commented Jul 2, 2020

Heey I think I workaround it -not 100% sure yet- 😂

I tried to run the converter function couple of times then take the last page.
I used this function as image generator

function generateImage() {
    return new Promise(resolve => {
        const doc = new jsPDF("p", "mm", "a4");
        const node = this.$refs.content;
        domtoimage.toPng(node)
            .then((page) => {
                doc.addImage(page, 'JPEG', 0, 0, 210, 297);
                return resolve(doc);
            })
    })
}

And use it like this:

generateImage().then(() => {
    console.log("first time")
    generateImage().then(() => {
        console.log("one more time to make sure ")
        generateImage().then((doc) => {
            console.log("okay now it should be fine")
            doc.save(`my_great_file.pdf`)
        })
    })
})

I just pushed this to our production, I hope it works fine with me.
*Note: it might take sometime, I add a loader just make it more user friendly

@DanielStout5
Copy link
Author

I really recommend just using html2canvas - it's more customizable, faster, and reliable. No need to generate the image multiple times, all elements are included in the first one. (I'm unaffiliated with that library)

@mikerodham
Copy link

mikerodham commented Jul 11, 2020

I really recommend just using html2canvas - it's more customizable, faster, and reliable. No need to generate the image multiple times, all elements are included in the first one. (I'm unaffiliated with that library)

I've just stopped using this because it's very unreliable on how it processes the image, text is weird and it's not supported on safari it seems. Shame.

Heey I think I workaround it -not 100% sure yet- 😂

I tried to run the converter function couple of times then take the last page.
I used this function as image generator

function generateImage() {
    return new Promise(resolve => {
        const doc = new jsPDF("p", "mm", "a4");
        const node = this.$refs.content;
        domtoimage.toPng(node)
            .then((page) => {
                doc.addImage(page, 'JPEG', 0, 0, 210, 297);
                return resolve(doc);
            })
    })
}

And use it like this:

generateImage().then(() => {
    console.log("first time")
    generateImage().then(() => {
        console.log("one more time to make sure ")
        generateImage().then((doc) => {
            console.log("okay now it should be fine")
            doc.save(`my_great_file.pdf`)
        })
    })
})

I just pushed this to our production, I hope it works fine with me.
*Note: it might take sometime, I add a loader just make it more user friendly

I've just leveraged this package in a similar way by calling it once, then a second time before returning the image to the user. Seems to work this way... however I don't expect this package to be updated any time soon so it'll do for now... 😬

@Nevoss
Copy link

Nevoss commented Jul 23, 2020

Heey I think I workaround it -not 100% sure yet- 😂

I tried to run the converter function couple of times then take the last page.
I used this function as image generator

function generateImage() {
    return new Promise(resolve => {
        const doc = new jsPDF("p", "mm", "a4");
        const node = this.$refs.content;
        domtoimage.toPng(node)
            .then((page) => {
                doc.addImage(page, 'JPEG', 0, 0, 210, 297);
                return resolve(doc);
            })
    })
}

And use it like this:

generateImage().then(() => {
    console.log("first time")
    generateImage().then(() => {
        console.log("one more time to make sure ")
        generateImage().then((doc) => {
            console.log("okay now it should be fine")
            doc.save(`my_great_file.pdf`)
        })
    })
})

I just pushed this to our production, I hope it works fine with me.
*Note: it might take sometime, I add a loader just make it more user friendly

Did not work for me... 😞. maybe some one find another solution?

@hyl1374961656
Copy link

domtoimage(node).then(dataUrl => {
domtoimage(node).then(dataUrl1 => {
console.log(dataUrl1)
})
})
很蠢,但是可以解决问题

@shameersalim
Copy link

Running domtoimage multiple times does not work with ios 13

@eddsaura
Copy link

eddsaura commented Dec 9, 2020

Still no solutions?

When I build my ionic app it doesn't work the first time, second time works flawlessly. It makes no sense.

There are no errors, I tried with try catch, no error neither... Idk

@haris-crewlogix
Copy link

This is a serious issue and needs to be addressed :( Stuck there....

@hyl1374961656
Copy link

html2canvas

@eddsaura
Copy link

html2canvas

For us it didn't work the same way, like the image was rescaled or something strange. We finally ended up launching 2 times the app at the very first time, hahaha I know...

@haris-crewlogix
Copy link

@eddsaura did you manage to solve the issue? Please share the approach with us as well :)

@christian-reichart
Copy link

Same problem! Seems to happen only on Safari on MacOS (not iOS)
I'm creating a GIF out of multiple images created by dom-to-image. The first render seems to miss the images included in my DOM, background color of my div is visible in the output.

@shameersalim
Copy link

shameersalim commented Mar 18, 2021

The problem occurs on iOS as well.
I ended up using html2canvas.

@LouiMinister
Copy link

@hyl1374961656
Thx bro.
I don't know why it works, but it works.

@ihoment-chentao
Copy link

any solution for this issue? html2canvas not support ssr so i have to search other way using domtoimage

@shameersalim
Copy link

shameersalim commented Jul 22, 2021 via email

@christian-reichart
Copy link

any solution for this issue? html2canvas not support ssr so i have to search other way using domtoimage

I ended up making a "disposable" first render with dom-to-image that I don't use basically. The second render then works.

@zhoustudent
Copy link

domtoimage(node).then(dataUrl => { domtoimage(node).then(dataUrl1 => { console.log(dataUrl1) }) }) 很蠢,但是可以解决问题

Hi, i try your answer,it`s working! But, do you know why it working?

@kittykatattack
Copy link

Hey Everyone! Just posting that, yes, this is still an issue with Safari (15.1) and, yes, the workaround is to run domtoimage twice. Here's how I'm doing it:

domtoimage.toJpeg(imageCard, {
    width: 1080,
    height: 1080,
    quality: 0.5,
  }).then(dataUrl => {
  domtoimage
    .toJpeg(imageCard, {
      width: 1080,
      height: 1080,
      quality: 0.5,
    })
    .then(dataUrl2 => {
      //Do your all stuff with dataUrl2...
    });
  });

Interestingly, I only had the missing images problem on the dataUrl that I upload to my API. Bafflingly, all images generated for local download are fine (domtoimage only needs to run once.)

This seems to be only a Safari issue.

Based on the suggestions in this thread, II did try html2canvas when I first encountered the missing images issue, but I don't recommend it: it's super-buggy, doesn't seem to like linear gradients, and I could not get a single image rendering with my setup. Maybe it will work for you?

I can live with the above workaround for now 😄.

@jefcoserrca
Copy link

@kittykatattack i had the same problem with iOS but your answer is correct i try this pice of code in my service and it works! Thanks
Problem fixed

ThereseKH added a commit to ThereseKH/Monthly-backdrop-generator that referenced this issue Dec 26, 2021
@sulmanazhar2
Copy link

Okay so after much testing I was finally able to solve this. Apparently, the images that were coming from the server were not rendering in safari when converting dom to the image. So after many tries, I came up with the solution of converting the image URL to base64 then using that string to render the dom element i want the image of. After that everything was local to that dom element which then solved my problem

 const exportAsImage = (ext) => {
    try {
      var node = document.getElementById(cause.id);
      var options = {
        quality: 1,
      };
      setLoading(true);

      domtoimage.toPng(node, options).then(function (dataUrl) {
        domtoimage.toPng(node, options).then(function (dataUrl) {
          var link = document.createElement("a");
          link.download = "infaque-impact.png";
          link.href = dataUrl;
          link.click();
          setLoading(false);
        });
      });
    } catch (err) {
      console.log(err.message);
    }
  };

const getBase64FromUrl = async (url) => {
  const base64 = await new Promise((resolve, reject) => {
    toDataUrl(url, function (myBase64) {
      resolve(myBase64); // myBase64 is the base64 string
    });
  });
  return base64;
};

export const toDataUrl = (url, callback) => {
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    var reader = new FileReader();
    reader.onloadend = function () {
      callback(reader.result);
    };
    reader.readAsDataURL(xhr.response);
  };
  xhr.open("GET", url);
  xhr.responseType = "blob";
  xhr.send();
};

@sportakal
Copy link

sportakal commented Sep 10, 2022

To convert images to base64 doesn't solve,. Still safari doesn't create images on first render.

I need to create canvas there are how many images in element, times. If images from external domain, it works fine. When images from local server, it needs to create many times.

Issue occurs on ipad and ios and macos.

It's serious problem. There must be a solution...

@shahidshaLumel
Copy link

even double rendering doesnt work now ,someone create a proper fix without double rendering because the time consumption is too long .

@MrOli3000
Copy link

Double rendering stopped working at some stage. Tried html2canvas but it doesn't work will with transform, rotate, overflow: 'hidden' and SVG. So all renders are really messy.

Still looking for a solution. Only occurring on Safari and iOS.

@Livshitz
Copy link

Calling domtoimage.toPng twice did fix for me on Safari and iOS.

@ESCUDIA
Copy link

ESCUDIA commented Jan 26, 2023

I recently started using the library and came up with this same error.
I think I've found the cause and it is not from the library.
Apparently each browser renders images with svg sources differently. In Safari the embedded images inside the svg appear to load very slowly the first time the browser encounters them, and sometimes they never load (unless you append the element visibly to the DOM).

The solution can be:

  • Call the domtoimage.toSvg() directly and append an image with that svg to the DOM
  • You have to wait for all the elements of the svg to load correctly before drawing it to a canvas (This is what the library fails to do on its built in .toPng, .toJpg, etc..).
  • From there you can take that image and draw it on a canvas, then convert it to another format.

The process is a little longer than using the built in functions of the library but results are a lot more consistent.

Code example:

domtoimage.toSvg($element).then(async (svgData) => { // Call the .toSvg function

    let $svgImg = new Image(); // Svg container img
    $svgImg.src = svgData; // Set the svg data as src

    const $canvas = document.createElement('canvas'); // Create a rendering canvas
    $canvas.width = $element.offsetWidth; // Set canvas width same as svg image
    $canvas.height = $element.offsetHeight; // Set canvas height same as svg image
    var ctx = $canvas.getContext("2d");
    
    document.appendChild($svgImg); // Append svg to the DOM for rendering

    $svgImg.onclick = async function () { // Wait for svg to render then click it

        ctx.drawImage($svgImg, 0, 0); // Draw the fully rendered svg to the canvas

        // Use canvas functionality to export its data (in this case to a png file)
        const dataUrl = $canvas.toDataURL(); 

        // Do what you need with the data
        let $pngImg = new Image();
        $pngImg.src = dataUrl;
        document.appendChild($pngImg); 

    };

});

I know this doesn't satisfy every use case, but if you can wait for a PREVIEW svg and then perform the conversion this can help.
Another thing to note: Sometimes even the preview SVG doesn't load correctly until you interact with the DOM (scroll, click, etc..).

@Whizzie99
Copy link

My fix was to stop using domtoimage and use html2canvas instead.

this worked well for me...Thank you so much!!

@zhongwangquan
Copy link

zhongwangquan commented May 30, 2023

I use this way to solve the problem;

only when all the images are loaded, and then use the API of toJpeg to create the canvas;
The reason may be SVG is so slow to render in ios/safari, so need all dom elements to be rendered, especially the image elements. By the way, I also use toJpeg twice, even though I don't know why;

I also try to html2canvas, even though it solved the problem of the image blank, because the library directly renders the image to canvas, not like the dom-to-image first converts the HTML element to SVG, and then converts canvas;
but its problems let me crazy, like not supporting many css3 features, text-overflow, image problems...

// The total num of needed load images
const totalLoadImgCount = useRef(0)
//  The num of loaded images
const loadSuccessImgCount = useRef(0)  

 const createImgUrl = async () => {
    await domtoimage.toJpeg(document.getElementById('share-dom') as HTMLElement)
    const imgUrl = await domtoimage.toJpeg(
      document.getElementById('share-dom') as HTMLElement
    )
   xxxxx
}
// When all images are loaded, then to convert the canvas
const onLoadImage = () => {
  loadSuccessImgCount.current++

  if (loadSuccessImgCount.current === totalLoadImgCount.current) {
    createImgUrl()
  }
}

//Count the totalLoadImgCount
useEffect(() => {
  options?.extends?.imgUrl && totalLoadImgCount.current++
  options?.extends?.initImageUrl && totalLoadImgCount.current++
}, [options])


return 
<div id="share-dom">
   <img
    className="w-full"
    src={toXSmallImage(options?.extends?.imgUrl as string)}
    onLoad={() => {
     onLoadImage()
    }}
   />
  <img
    className="relative w-full"
    id="initImageUrl"
    src={toXSmallImage(options?.extends?.initImageUrl as string)}
    onLoad={() => {
      onLoadImage()
    }}
  />
</div>)

@hebertlima
Copy link

hebertlima commented Jun 24, 2023

I have similar issue in safari when the url is like:

<img src="blob:http://localhost...."

it renders normally in the browser, but when downloading the image disappears, but in chrome it works perfectly, btw do not replace it with html2canvas, this library is far far superior. html2canvas is full of bugs and barely renders CSS properties. this library has much better compatibility with CSS styles than html2canvas.

@sulmanazhar2 Thanks! It works gracefully!

@AmirDiafi
Copy link

My solutions is adding the target dom width/height:

const onCapture = useCallback(async (domId: string) => {
    try {
      setIsLoading(true)
      const domAvatar = document.getElementById(domId)
      if (!domAvatar) {
        return
      }

      const options = {
        width: domAvatar.clientWidth,
        height: domAvatar.clientHeight,
      }

      const dataUrl = await domToImage.toPng(domAvatar, options)

      setImageUrl(dataUrl)
    } catch (error) {
      console.error(error)
    } finally {
      setIsLoading(false)
    }
  }, [avatarRef?.current])

  return {
    isLoading,
    onCapture,
    imageUrl,
    avatarRef,
  }
}

@349989153
Copy link

domtoimage(node).then(dataUrl => { domtoimage(node).then(dataUrl1 => { console.log(dataUrl1) }) }) 很蠢,但是可以解决问题

草你他妈简直是天才

@csj1328059093
Copy link

csj1328059093 commented Oct 24, 2023

        let dataUrl = await domtoimage.toPng(node, params)
        for (let i = 0; i < 10; i++) {
            // When a blank background appears, the image size will be much smaller than before. Find a value that can determine whether the generation was successful
            if (dataUrl.length / 1024 > 200) {
                break
            } else {
                dataUrl = await domtoimage.toPng(node, params)
            }
        }

@sundong212
Copy link

domtoimage(node).then(dataUrl => { domtoimage(node).then(dataUrl1 => { console.log(dataUrl1) }) }) 很蠢,但是可以解决问题

兄弟,真牛逼,我也是连续生成然后双数index的就可以,没想到居然可以这么解决

@abdul-yudi
Copy link

abdul-yudi commented Jul 4, 2024

if not works on ios, please try this

  domtoimage.toJpeg(captureWrapper, {
  }).then(() => {
    domtoimage.toJpeg(captureWrapper, {
    }).then(() => {
      domtoimage.toJpeg(captureWrapper, {
      }).then(imgDataUrl => {
        console.log(imgDataUrl);
      });
    });
  });

this code solved my problem

@forever0714yuan
Copy link

forever0714yuan commented Jul 4, 2024 via email

@ecancil
Copy link

ecancil commented Jul 19, 2024

        let dataUrl = await domtoimage.toPng(node, params)
        for (let i = 0; i < 10; i++) {
            // When a blank background appears, the image size will be much smaller than before. Find a value that can determine whether the generation was successful
            if (dataUrl.length / 1024 > 200) {
                break
            } else {
                dataUrl = await domtoimage.toPng(node, params)
            }
        }

This was a GREAT approach

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

No branches or pull requests