niklasvh / html2canvas

Screenshots with JavaScript
https://html2canvas.hertzen.com/
MIT License
30.42k stars 4.79k forks source link

Render off-screen elements #117

Closed itsravenous closed 7 years ago

itsravenous commented 12 years ago

First - thanks for your hard work - this is an extremely useful library :)

I'm looking to render an offscreen element onto canvas, but this just results in a blank image. I assume this is because html2canvas renders the whole (visible) page and then crops down to the specified element, meaning anything that's outside the page boundaries is missed.

I suspect this requires a rewrite of how html2canvas handles rendering a specific element as opposed to the whole page, so I appreciate it's a big ask.

Cheers Tom

niklasvh commented 12 years ago

Is the element visible by scrolling on the page? What element are you passing to html2canvas as the element to render?

itsravenous commented 12 years ago

No, it's absolutely positioned and off-screened with left: -9999px. The element in question is a bar chart I created with some divs. It renders to canvas fine when on-screen.

So, the reason I'm off-screening it: The image generated is going to (at some point) be printed off, and so needs to be reasonably high-res. Thus I clone the chart, put it offscreen and enlarge it by a factor of 2 (using font-size and settings widhts/heights - I found using a CSS3 scale transform resulted in a blank image too, even if it was on-screen), and then call html2canvas on it.

niklasvh commented 12 years ago

Think a quick solution would be to position it on the page right before you call html2canvas on it, and then move it away right after. I'll have to look through the code to see where exactly it stops the off-screen elements to be rendered, but if you were calling html2canvas on the body element, the expected result would be to not have the item visible.

As for the scale CSS property, none of the CSS3 transorms work currently, but will hopefully be implemented, at least in part, at some point.

itsravenous commented 12 years ago

Aah, thanks - I'll try that out! Thanks for the info :)

wallacecDundas commented 11 years ago

Will you be fixing the problem? http://jsfiddle.net/makubex/DZDGw/2/

Or perhaps at least add a way to handle the error?

niklasvh commented 11 years ago

What exactly is the issue in your example? Seems to be doing what it should (tested with Chrome)

wallacecDundas commented 11 years ago

When the element is off-screen on the left, html2canvas just crashes, and provides no means of error handling. Chrome lets the exception go and onrendered is still fired, but in firefox it fails, it just throws the exception and stop executing the code.

The example is to demonstrate that :

  1. there is a problem rendering DOM elements that are left-off-screen and top-off-screen(not shown) but not right-off-screen and bottom-off-screen
  2. although 1. can be worked around by moving the elements into view and then take a snapshot, there is no way to know if the snapshot has failed. In my cases i need to know which ones have failed to take a snapshot, remember it, and when it becomes visible on-screen, it takes a snap shot.
yojoe26 commented 10 years ago

I'm having a similar issue...I have a scrollable modal that I want to convert to canvas. When running html2canvas, it only captures the visible portion of the screen, truncating everything hidden from the scroll. Setting the height attribute has no impact.

parnelle89 commented 10 years ago

@yojoe26, try using the scrollIntoView function to move the screen around as html2canvas is parsing it. I had a similar problem, and found a solution. Very hacky solution, and would not recommend if "scrolling wildly around the page" is going to be a user experience issue!

I added "options.elements[0].scrollIntoView(false);" as the first line in the "renderElement" function right before the variable declarations.

yojoe26 commented 10 years ago

@parnelle89 - thanks for the tip!

toannguyen commented 9 years ago

@parnelle89 thanks very much! "options.elements[0].scrollIntoView(false);"

zzxian commented 9 years ago

@parnelle89 "options.elements[0].scrollIntoView(false);" ? Can you give me an example with a whole js snippet? I am new to html2canvas. Thanks!

herringtown commented 9 years ago

@niklasvh regarding the question above, can you tell me where in the code you specify the stopping point for off-screen elements to be rendered ?

herringtown commented 9 years ago

by default, html2canvas uses the current viewport as the size of the canvas to be rendered (for obvious reasons). If, for instance, you want to render a particular element on the page that extends pass the fold -- or is entirely below the fold -- you can utilize the width/height options in the html2canvas call to render the element in it's entirety (not just the part that's within the viewport).

As long as the item is ulimately scrollable (and not, for example, positioned absolutely at something like left: -9999px) this should work :

var useWidth = document.getElementById("myElementThatExtendsBeyondTheViewport").style.width;
var useHeight = document.getElementById("myElementThatExtendsBeyondTheViewport").style.height;

html2canvas(element, {
    width: useWidth,
    height: useHeight,
    onrendered: function(canvas) {
        // you should get a canvas that includes the entire element -- not just the visible portion
    }
});

If the element IS absolutely positioned offscreen -- as in @itsravenous 's case -- I would temporarily add the following CSS to make it renderable by html2canvas (but still invisible -- just past the viewport):


var myOffscreenEl = document.getElementById("myOffscreenEl"),
    useWidth      = myOffscreenEl.style.width,
    useHeight     = myOffscreenEl.style.height;

// position it relatively, just below the fold..
myOffscreenEl.style.position = 'relative';
myOffscreenEl.style.top = window.innerHeight + 'px';
myOffscreenEl.style.left = 0;

html2canvas(myOffscreenEl, {
    width: useWidth,
    height: useHeight,
    onrendered: function(canvas) {

       // restore the old offscreen position
      myOffscreenEl.style.position = 'absolute';
      myOffscreenEl.style.top = 0;
      myOffscreenEl.style.left = "-9999px"

    }

});

^^ seems potentially simpler/safer than @parnelle89's solution, but I might be missing something here...

EDIT: I'm encountering some scenarios with my solution where some extremely tall elements get cropped unless if I first clone the element with element.cloneNode and then append it to body. Not sure why..

ejlocop commented 9 years ago

hi im facing the same problem, but the above solution didnt work for me.. i have a scrollable div content and i want to capture the whole content. not the one in the viewport only.. can someone help me ?

ejlocop commented 9 years ago

1 2

why is mine is like this ? T_T

eworksmedia commented 9 years ago

We solved this by changing line 604 from

return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {

to

return renderDocument(node.ownerDocument, options, options.width != undefined ? options.width : node.ownerDocument.defaultView.innerWidth, options.height != undefined ? options.height : node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {

Basically force it to respect the width/height arguments set during setup rather than getting the viewable area according to the document.. No weird positioning hacks necessary..

ioRekz commented 9 years ago

Hello, I'm trying to render an element that is offscreen (need to scroll to see it). I don't capture the whole page, only one element. Should it work or I need to tweak some things ?

btm1 commented 9 years ago

+1 this is the issue i'm having

ghost commented 8 years ago

@eworksmedia solution above worked for me. Great stuff thanks

alana314 commented 8 years ago

I had to put my canvas in an iframe, run html2canvas inside the iframe, run toDataURL(), and pass the image back to the parent.

OscarAgreda commented 8 years ago

@eworksmedia Thank you , good code. works like a charm.

gregoryduckworth commented 7 years ago

@eworksmedia your fix worked for me as well.

Trexology commented 7 years ago

@eworksmedia MAGIC! it works perfectly!!

codebyuma commented 7 years ago

Is the fix that @eworksmedia mentioned (to line 604) in any of the available releases? Or is this a manual change everyone's making to get this to work?

gururajmca commented 7 years ago

Thanks @eworksmedia for solution, I am able to see the canvas getting rendered, but it's very small in size, I have tried giving width and height params but still image is very small, is there any way we could scale the image?

gururajmca commented 7 years ago

@codebyuma this change we need to update manually.

AlexGrump commented 7 years ago

My fix is strange, but it work CSS: .html2canvas-container { width: 3000px !important; height: 3000px !important; }

Brieg commented 7 years ago

+1

Romerski commented 7 years ago

@eworksmedia not working for me :( Still not rendering off-screen width gantt chart who im wanting to render.

Any help?

gururajmca commented 7 years ago

@Romerski make sure your container is set visibility: visible;

Romerski commented 7 years ago

@gururajmca thanks but still not working :( Here is an image: http://i.imgur.com/EE8GyHD.png

Green: Popup when I click print button on Gantt chart. Blue: HTML gantt chart who I want to render. Red: What is rendering html2canvas from html (only screen visible part of html)

I tried to set visibility and overflow attributes to html gantt's div and to canvas div but nothing happens :(

Thanks again!!

gururajmca commented 7 years ago

@Romerski Can you try this one, which worked me.

//Default css

hidden_div {

display: none;
visibility: hidden;
position: absolute;
top: 0;
left: 0;

}

function HtmltoImage() { $('#hidden_div').css("display" , "block"); $('#hidden_div').css("visibility" , "visible");

var defer = $q.defer();
html2canvas($('#hidden_div'), 
{
    useCORS: true,
    allowTaint: true,
    letterRendering: true,
    logging:true,
    onrendered: function (canvas) {
        $('#hidden_div').css("overflow" , "hidden");
        var quality =[0.0,1.0];
        img = canvas.toDataURL('image/png',quality);
        var data = {
            "pngdata" : img,
        };
        defer.resolve(data);
    }
});
return defer.promise;

}

Romerski commented 7 years ago

@gururajmca thanks for your help but unfortunately still not working... I have no problem with the height, is rendering well but the problem is with the width, which is not rendering off-screen width of gantt chart.

Im pretty sure that is something related to the gantt chart overflow, This gantt chartt which im rendring is via JQuery.gantt(). I'll keep investigating.

Appreciate it any help.

battlesteel commented 7 years ago

@eworksmedia solution didn't work for me.

I fixed mine with this code.

function generateToPdf(element, fileName, method) {
    var c = document.getElementById(element);
    // overwrite owner doc inner height with your div clientHeight
    c.ownerDocument.defaultView.innerHeight = c.clientHeight;
    c.ownerDocument.defaultView.innerWidth = c.clientWidth;
    if (method === 'canvas') {
        html2canvas(c, {
            onrendered: function (canvas) {
                document.body.appendChild(canvas);
                // scale paper height based on ratio new canvas height and width
                var paperHeight = 210 * (canvas.height / canvas.width);
                var paperFormatInMm = [210, paperHeight];
                var doc = new jsPDF('p', 'mm', paperFormatInMm);
                doc.addImage(canvas, 'PNG', 2, 2);
                doc.save(fileName + '.pdf');
            }
        });
    }
}
wotomas commented 7 years ago

Its a closed issue, but some people like me could searching for the same issue, so i'll just add an comment in here. Solution as suggested by @eworksmedia, I created an npm module for personal use, but feel free use.

npm install --save html2canvas-render-offscreen

and use it just like html2canvas

baylock commented 6 years ago

I'm not a developper or anything but I had the same issue and none of your fixes worked for me. But then I tried something very simple: instead of putting the container off screen, I just "z-indexed" it below everything else. This way, it was technically on screen for the script to work, even though it was not visible to the final user. I'm pretty sure there are some side effects to this, according to your own layout but in my case, it worked just fine.

rickli1989 commented 5 years ago

@eworksmedia You are legend !!!!!!

DougHenrique commented 5 years ago

The solution below worked for me, but it works only in the web browser, if I try to download the pdf through the mobile application (using WebView) does not work, someone else has gone through something similar and know a way to correct?

Note: sorry my English I am using Google Translate

foxhehehe commented 5 years ago

@battlesteel Your solution worked for me, others didn't Thks !

@eworksmedia solution didn't work for me.

I fixed mine with this code.

function generateToPdf(element, fileName, method) {
    var c = document.getElementById(element);
    // overwrite owner doc inner height with your div clientHeight
    c.ownerDocument.defaultView.innerHeight = c.clientHeight;
    c.ownerDocument.defaultView.innerWidth = c.clientWidth;
    if (method === 'canvas') {
        html2canvas(c, {
            onrendered: function (canvas) {
                document.body.appendChild(canvas);
                // scale paper height based on ratio new canvas height and width
                var paperHeight = 210 * (canvas.height / canvas.width);
                var paperFormatInMm = [210, paperHeight];
                var doc = new jsPDF('p', 'mm', paperFormatInMm);
                doc.addImage(canvas, 'PNG', 2, 2);
                doc.save(fileName + '.pdf');
            }
        });
    }
}
andylighthouse commented 5 years ago

We solved this by changing line 604 from

return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {

to

return renderDocument(node.ownerDocument, options, options.width != undefined ? options.width : node.ownerDocument.defaultView.innerWidth, options.height != undefined ? options.height : node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {

Basically force it to respect the width/height arguments set during setup rather than getting the viewable area according to the document.. No weird positioning hacks necessary..

What is line 604? can you give some context? I can't find line 604 anywhere

andreasvirkus commented 4 years ago

@andylighthouse see 5 comments above: https://github.com/niklasvh/html2canvas/issues/117#issuecomment-323076300

ProteanDev commented 4 years ago

Still having this issue

eljeffeg commented 3 years ago

Was having this issue still on version 1.0.0-rc.7. My element to convert is at the top of the page. If html2canvas is run when the user has scrolled down, it would crop the top of the image off. I tried @wotomas / @eworksmedia solution and while it fixed the cropping, the captured text was improperly positioned (as if it was forced down similar to the crop). In conjunction with html2canvas-render-offscreen window.scrollTo(0, 0) did the trick to fix the text, but I don't want to jump the user back up to the top. Hope this gets a solution soon. Honestly, I'd like to get the entire thing working without any visibility as I'm using it in an Electron app, generating thumbnail images on the fly based on changes made.

Ultimately, I ended up using html2canvas-render-offscreen and floating it top, left of 0, 0 with a z-index that placed it under everything. Thanks for the great tool.

HenrijsS commented 3 years ago

This has been an issue for 6-8 years and still isn't fixed. Gosh. There are multiple solutions posted above. Why aren't they being implemented?

nishantgupta11 commented 1 year ago

-> Add CSS overflow-y: scroll; to HTML element pdfHTML -> Add attribute width & windowWidth to html2canvas method, which should be equal to width of pdfHTML Div

html2canvas(document.querySelector(".pdfHTML"), {
    width: 1350, windowWidth: 1350
}).then(canvas => {
    base64stringpdf = canvas.toDataURL("image/jpeg");
});
nishantgupta11 commented 1 year ago

-> Add CSS overflow-y: scroll; to HTML element pdfHTML -> Add attribute width & windowWidth to html2canvas method, which should be equal to width of pdfHTML Div

html2canvas(document.querySelector(".pdfHTML"), {
    width: 1350, windowWidth: 1350
}).then(canvas => {
    base64stringpdf = canvas.toDataURL("image/jpeg");
});