html2canvas
html2canvas copied to clipboard
Stuck at "0ms Starting document clone" on IOS (Safari and Chrome)
I can reproduce the issue on multiple ios devices. Works like a charm on all other OS (including Android, Windows and MacOS) though. I have tried Safari and chrome and the issue showed up on both browsers. I've tried using all JS versions (non-minified) from 1.4.1 down to 1.2.0. Can't see any errors in the console either except it being stuck at "Starting document clone" at 0ms. I'm trying to screenshot a html div element that contains multiple divs (mix of imgs and text). The size of png generated on other devices is not more than 100kb though so I doubt it's got something to do with render issue due to large size as mentioned in another issue here.
This is what I'm getting in chrome console DEBUG #1 0ms Starting document clone with size 428x859 scrolled to 0,-526 And nothing happens after that.
Here's my script
Version html2canvas : 1.4.1 IOS : 16.4.1
Ran into the same issue. Seems to be affecting WebKit browsers.
Tested on GNOME Web (evince
): Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15
.
I managed to figure out that in my case it stopped working after I started to use Next.js’s Image
on the page. 😕 (I’m using [email protected]
). Image was not used in part of the page I was capturing. After I’ve added it html2canvas
promise never resolved.
Yeah It's weird. I've tried multiple things but couldn't get it move forward on iOS devices. Finally, decided to use domtoimage instead. It's working fine but not the best solution. It's slower and is not as reliable as html2canvas.
@marek-saji I use nextimage without optimisation. and it doesn't work either. Did you manage to fix it somehow?
as far as i have noticed it is not an issue of next image, but of images loaded using lazy loading. If there is a lazy loading image on the page, this causes a problem with html2canvas on safari
My html2canvas was also was hanging in Safari (macOS/iOS) at #1 – "0ms" – "Starting document clone with size..." with no other error or proceeding on the console. I came accross this post and found @AdrianEasyOze 's answer was the issue. I could understand it because it used to work before and stopped only after I lazy loaded one image on the site. The image is not even inside the element to screenshot yet Safari was failing with html2canvas somehow. On removing the lazyload it started working again. Chrome / Edge do not have this problem.
My html2canvas was also was hanging in Safari (macOS/iOS) at #1 – "0ms" – "Starting document clone with size..." with no other error or proceeding on the console. I came accross this post and found @AdrianEasyOze 's answer was the issue. I could understand it because it used to work before and stopped only after I lazy loaded one image on the site. The image is not even inside the element to screenshot yet Safari was failing with html2canvas somehow. On removing the lazyload it started working again. Chrome / Edge do not have this problem.
this is not the solution, it still not working. I have 1 image and one div
block. On other devices it's work perfect. But on mobile IOS in Safari or Chrome - i get freeze on "0ms Starting document clone". I don't have lazy load images and etc.
@petermarkovich If you completely remove a photo from a particular screen, does the problem still exist? Can I see how you add the photo (some code)?
@petermarkovich If you completely remove a photo from a particular screen, does the problem still exist? Can I see how you add the photo (some code)?
yep,
i use react + magento. My react app it's a part of magento page. When i test only my app without magento 2, і don't have this issue
example:
style={{ backgroundImage:
url(${image}), backgroundSize: "cover", backgroundRepeat: "no-repeat", backgroundPosition: "top", }}
or src={image}
the image = path to image || base64
I have a similar issue on Windows Chrome, after trying to make screenshot of the same part multiple times
@petermarkovich If you completely remove a photo from a particular screen, does the problem still exist? Can I see how you add the photo (some code)?
yep, i use react + magento. My react app it's a part of magento page. When i test only my app without magento 2, і don't have this issue example:
style={{ backgroundImage:
url(${image}), backgroundSize: "cover", backgroundRepeat: "no-repeat", backgroundPosition: "top", }}
or
src={image}
theimage = path to image || base64
.
but this is super strange. In prod server i have this issue. In local env with production mode - there is no errors ...
We had issues with lazy loaded images anywhere in the dom, not just within the element targeted for export. No error logs, but the output logs show that the process hangs before the dom gets cloned
Same issue raised today, caused by at least one image in the current document that has loading="lazy"
👍🏼
Same issue. When setting { useCORS: true }
problem is solved.
html2canvas(document.body, { useCORS: true })
I had this issue. useCors did not resolve. I was generating charts and converting them for pdfs. All that was required on my side was to add like a 2 second delay between when the charts were generated and when i tried to convert them.
Indeed - useCORS solution didn't help at all. On the other hand, loading='lazy'
was the problem by me. Even though (as mentioned) the img that was lazy loaded, was completely out of the screenshoted part of HTML.
But since the plugin clones the whole document (as far as I understand it), it really doesn't matter if the lazy loaded img is in the screenshoted part, or somewhere else.
When I use version 1.0.0-rc.4, the issue is no longer present.
Html2canvas Safari Problem: Not Working
I came to you with a complete solution proposal. It worked perfectly for me. I couldn't find it anywhere on the internet, I finally solved it with my own methods.
Problem: The problem is caused by CORS policy on Safari and LazyLoad. It could also be due to just one of them. Depending on the situation, you can edit the function, primarily LazyLoad.
Solution: You need to exclude external URLs that may be against CORS policy and any LazyLoad images on the page, even if they are not in Canvas.
Solution Code:
html2canvas(element, {
useCORS: true, allowTaint: true, ignoreElements: function (e) {
// Here, ignore external URL links and lazyload images
if ((e.tagName === "A" && e.host !== window.location.host) || e.getAttribute('loading') === "lazy") {
return true;
} else {
return false;
}
}
}).then(function (canvas) {
// Make what do you want with canvas data.
})
My Function (Start Download With a Button, Copy a Display None Div):
/*
Function Parameters:
elementID: The element you want to copy.
buttonID: Button to start the process
name: File name for save
toggleDisplay: If the element you want to convert to Canvas is Display: None, you should choose True. Thus, it will translate as Display: Block before the process starts. After copying, Display: will be converted back to None. If your element to be copied is already visible, select False.
titleHtml: It changes the HTML on the button after the download starts.
titleWait: Changes the HTML on the button during the process.
*/
function downloadHtmlToImage(elementID, buttonID, name, toggleDisplay = true, extension = 'jpg',titleHtml = '<i class="me-2 bi bi-check"></i>Download Started',titleWait = '<i class="fa fa-spin fa-spinner me-2"></i>Wait...') {
let button = $('#' + buttonID)[0];
$(button).html(titleWait);
$(button).attr('disabled', 'disabled');
var element = document.querySelector("#" + elementID);
if(toggleDisplay){
element.style.display = 'block';
}
// With ignoreElements, one of the html2canvas parameters, we exclude external URL and loading="lazy" images on the entire page. You can change this as you wish. If you have to use a LazyLoad image, you can remove all LazyLoad attributes from the page before processing.
html2canvas(element, {
useCORS: true, allowTaint: true, ignoreElements: function (e) {
if ((e.tagName === "A" && e.host !== window.location.host) || e.getAttribute('loading') === "lazy") {
return true;
} else {
return false;
}
}
}).then(function (canvas) {
let mimeType = null;
if (extension === 'jpg' || extension === 'jpeg') {
mimeType = 'image/jpeg';
} else if (extension === 'png') {
mimeType = 'image/png';
} else {
extension = 'jpg';
mimeType = 'image/jpeg';
}
var a = $("<a style='display:none' id='js-downloder'>")
.attr("href", canvas.toDataURL(mimeType))
.attr("download", name + '.' + extension)
.appendTo("body");
a[0].click();
a.remove();
$(button).html(titleHtml);
$(button).removeAttr('disabled');
})
if(toggleDisplay){
element.style.display = 'none';
}
}
Here, example HTML:
<button role="button" onclick="downloadHtmlToImage('myElementForDownload','downloadButtonID','fileNameDownloaded')" id="downloadButtonID">My Button for Start Download</button>
Html2canvas Safari Problem: Not Working
I came to you with a complete solution proposal. It worked perfectly for me. I couldn't find it anywhere on the internet, I finally solved it with my own methods.
Problem: The problem is caused by CORS policy on Safari and LazyLoad. It could also be due to just one of them. Depending on the situation, you can edit the function, primarily LazyLoad.
Solution: You need to exclude external URLs that may be against CORS policy and any LazyLoad images on the page, even if they are not in Canvas.
Solution Code:
html2canvas(element, { useCORS: true, allowTaint: true, ignoreElements: function (e) { // Here, ignore external URL links and lazyload images if ((e.tagName === "A" && e.host !== window.location.host) || e.getAttribute('loading') === "lazy") { return true; } else { return false; } } }).then(function (canvas) { // Make what do you want with canvas data. })
My Function (Start Download With a Button, Copy a Display None Div):
/* Function Parameters: elementID: The element you want to copy. buttonID: Button to start the process name: File name for save toggleDisplay: If the element you want to convert to Canvas is Display: None, you should choose True. Thus, it will translate as Display: Block before the process starts. After copying, Display: will be converted back to None. If your element to be copied is already visible, select False. titleHtml: It changes the HTML on the button after the download starts. titleWait: Changes the HTML on the button during the process. */ function downloadHtmlToImage(elementID, buttonID, name, toggleDisplay = true, extension = 'jpg',titleHtml = '<i class="me-2 bi bi-check"></i>Download Started',titleWait = '<i class="fa fa-spin fa-spinner me-2"></i>Wait...') { let button = $('#' + buttonID)[0]; $(button).html(titleWait); $(button).attr('disabled', 'disabled'); var element = document.querySelector("#" + elementID); if(toggleDisplay){ element.style.display = 'block'; } // With ignoreElements, one of the html2canvas parameters, we exclude external URL and loading="lazy" images on the entire page. You can change this as you wish. If you have to use a LazyLoad image, you can remove all LazyLoad attributes from the page before processing. html2canvas(element, { useCORS: true, allowTaint: true, ignoreElements: function (e) { if ((e.tagName === "A" && e.host !== window.location.host) || e.getAttribute('loading') === "lazy") { return true; } else { return false; } } }).then(function (canvas) { let mimeType = null; if (extension === 'jpg' || extension === 'jpeg') { mimeType = 'image/jpeg'; } else if (extension === 'png') { mimeType = 'image/png'; } else { extension = 'jpg'; mimeType = 'image/jpeg'; } var a = $("<a style='display:none' id='js-downloder'>") .attr("href", canvas.toDataURL(mimeType)) .attr("download", name + '.' + extension) .appendTo("body"); a[0].click(); a.remove(); $(button).html(titleHtml); $(button).removeAttr('disabled'); }) if(toggleDisplay){ element.style.display = 'none'; } }
Here, example HTML:
<button role="button" onclick="downloadHtmlToImage('myElementForDownload','downloadButtonID','fileNameDownloaded')" id="downloadButtonID">My Button for Start Download</button>
loading="lazy" is the default attribute of the next/image We can move it to loading="eager"
loading="lazy" is the default attribute of the next/image We can move it to loading="eager"
In fact, with a little more effort, you can do it like this.
- Before Html2Canvas starts processing, you remove all HTML elements that have the value loading="lazy" and replace it with any value. For example: myCustomAttribute="itWillReplace"
- Html2Canvas works. In this process, there is no problem with CORS or LazyLoad in the structure retrieved from the DOM.
- Instead of myCustomAttribute="itWillReplace", we can replace it with the tag loading="lazy" wherever it is present.
Just a solution suggestion in a very primitive way :)
When I use version 1.0.0-rc.4, the issue is no longer present.
1.0.0-rc.4 produces blank image on ios
Hi guys, I had the same problem as You on Safari and indeed changing the loading
attributes of images to eager
solves the problem. I wanted to use the 'onclone' method from options as I found it more intuitive, but it seems to be called too late so instead - I change the attributes of all the images inside the captured dom node to 'eager' mode.
Here is a snippet that worked perfeclty for me (of course after taking the screenshot, you can change those attributes back):
const getElementImage = async (sourceElement: HTMLElement) => {
Array.from(sourceElement.querySelectorAll('img'))?.forEach((img) => {
if (img.getAttribute('loading') === 'lazy') img.setAttribute('loading', 'eager')
});
const canvas = await html2canvas(sourceElement, {
useCORS: true,
allowTaint: true,
logging: true,
height: sourceElement.clientHeight || window.innerHeight,
width: sourceElement.clientWidth || window.innerWidth,
ignoreElements: (el) =>
el.nodeName.toLowerCase() === 'canvas' ||
el.getAttribute('loading') === 'lazy'
});
const base64 = canvas.toDataURL('image/jpeg', 1.0);
const image = new Image();
image.width = sourceElement.offsetWidth || sourceElement.clientWidth;
image.height = sourceElement.offsetHeight || sourceElement.clientHeight;
image.src = base64;
return image
}
Hi! I just wanted to chime in that removing lazy loading from all images in my site resolved this issue. Only iOS was not exporting an image. I'm using NextJS with the next/image tag. Adding priority={true}
to all <Image />
tags totally resolved this issue.
Hi guys, I had the same problem as You on Safari and indeed changing the
loading
attributes of images toeager
solves the problem. I wanted to use the 'onclone' method from options as I found it more intuitive, but it seems to be called too late so instead - I change the attributes of all the images inside the captured dom node to 'eager' mode. Here is a snippet that worked perfeclty for me (of course after taking the screenshot, you can change those attributes back):const getElementImage = async (sourceElement: HTMLElement) => { Array.from(sourceElement.querySelectorAll('img'))?.forEach((img) => { if (img.getAttribute('loading') === 'lazy') img.setAttribute('loading', 'eager') }); const canvas = await html2canvas(sourceElement, { useCORS: true, allowTaint: true, logging: true, height: sourceElement.clientHeight || window.innerHeight, width: sourceElement.clientWidth || window.innerWidth, ignoreElements: (el) => el.nodeName.toLowerCase() === 'canvas' || el.getAttribute('loading') === 'lazy' }); const base64 = canvas.toDataURL('image/jpeg', 1.0); const image = new Image(); image.width = sourceElement.offsetWidth || sourceElement.clientWidth; image.height = sourceElement.offsetHeight || sourceElement.clientHeight; image.src = base64; return image }
work for me! But I don't have a loading node in my dom, Anyway, it works for me!
Html2canvas Safari Problem: Not Working
I came to you with a complete solution proposal. It worked perfectly for me. I couldn't find it anywhere on the internet, I finally solved it with my own methods.
Problem: The problem is caused by CORS policy on Safari and LazyLoad. It could also be due to just one of them. Depending on the situation, you can edit the function, primarily LazyLoad.
Solution: You need to exclude external URLs that may be against CORS policy and any LazyLoad images on the page, even if they are not in Canvas.
Solution Code:
html2canvas(element, { useCORS: true, allowTaint: true, ignoreElements: function (e) { // Here, ignore external URL links and lazyload images if ((e.tagName === "A" && e.host !== window.location.host) || e.getAttribute('loading') === "lazy") { return true; } else { return false; } } }).then(function (canvas) { // Make what do you want with canvas data. })
My Function (Start Download With a Button, Copy a Display None Div):
/* Function Parameters: elementID: The element you want to copy. buttonID: Button to start the process name: File name for save toggleDisplay: If the element you want to convert to Canvas is Display: None, you should choose True. Thus, it will translate as Display: Block before the process starts. After copying, Display: will be converted back to None. If your element to be copied is already visible, select False. titleHtml: It changes the HTML on the button after the download starts. titleWait: Changes the HTML on the button during the process. */ function downloadHtmlToImage(elementID, buttonID, name, toggleDisplay = true, extension = 'jpg',titleHtml = '<i class="me-2 bi bi-check"></i>Download Started',titleWait = '<i class="fa fa-spin fa-spinner me-2"></i>Wait...') { let button = $('#' + buttonID)[0]; $(button).html(titleWait); $(button).attr('disabled', 'disabled'); var element = document.querySelector("#" + elementID); if(toggleDisplay){ element.style.display = 'block'; } // With ignoreElements, one of the html2canvas parameters, we exclude external URL and loading="lazy" images on the entire page. You can change this as you wish. If you have to use a LazyLoad image, you can remove all LazyLoad attributes from the page before processing. html2canvas(element, { useCORS: true, allowTaint: true, ignoreElements: function (e) { if ((e.tagName === "A" && e.host !== window.location.host) || e.getAttribute('loading') === "lazy") { return true; } else { return false; } } }).then(function (canvas) { let mimeType = null; if (extension === 'jpg' || extension === 'jpeg') { mimeType = 'image/jpeg'; } else if (extension === 'png') { mimeType = 'image/png'; } else { extension = 'jpg'; mimeType = 'image/jpeg'; } var a = $("<a style='display:none' id='js-downloder'>") .attr("href", canvas.toDataURL(mimeType)) .attr("download", name + '.' + extension) .appendTo("body"); a[0].click(); a.remove(); $(button).html(titleHtml); $(button).removeAttr('disabled'); }) if(toggleDisplay){ element.style.display = 'none'; } }
Here, example HTML:
<button role="button" onclick="downloadHtmlToImage('myElementForDownload','downloadButtonID','fileNameDownloaded')" id="downloadButtonID">My Button for Start Download</button>
Thanks, it worked for me <3
i have a solutions for you, just before use html2canvas code load this to change all images from lazy load to eager load:
const lazyElements = document.querySelectorAll('[loading="lazy"]');
lazyElements.forEach(element => {
element.setAttribute('loading', 'eager');
});
I ended up implementing a similar solution, a variable called isScreenshotMode
that allows me to make subtle layout changes, as well as toggling my next/image to non-lazy load for the html2canvas function.
For me , its stuck at 0ms, and page gets refreshed always in ios safari and chrome, Any solution ?