niklasvh / html2canvas

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

Exporting using Leaflet #567

Closed JAPiasente closed 6 years ago

JAPiasente commented 9 years ago

Has anyone attempted to use html2canvas with leaflet. I have been able to get html2canvas to work with google maps but I now need to get it to work for leaflet, when I pan the map left, right, up or down I am missing some of the tiles from leaflet. Also I no longer get my overlay. I have created a crude jsfiddle to show the missing tiles problem. Also attached is a screen shot from my code missingtiles

https://jsfiddle.net/japiasente/7ybndo5L/

brcontainer commented 9 years ago

Provide a example the problem using http://jsfiddle.net

JAPiasente commented 9 years ago

I added the jsfiddle. https://jsfiddle.net/japiasente/7ybndo5L/

CraigVA commented 9 years ago

It takes a few ugly hacks to get it to work. Here's a simple example without any fixes:

http://jsfiddle.net/djwbra47/

There are several problems:

  1. The circular marker gets clipped to a quarter-circle. It looks like html2canvas is clipping elements BEFORE any transforms are applied. The marker would be centered at 0,0 without any transformations, so it gets clipped to the quarter-circle that would be visible at that position. Then afterwards html2canvas transforms it to the actual location. I fixed it by setting the left and top margins to 0 instead of negative (so the entire marker would be visible at 0,0), then adjusting the transformations by the same amount. (And then change those values back after the canvas is grabbed, of course.)
  2. The line shows up in the wrong place. It looks like html2canvas is applying transformations on the SVG paths TWICE. Without any fixes, the line shows up too far to the upper left. If you change its transformation to 0, it shows up the same distance too far to the lower right. I had to set the transformation to half of the original values to get it to work.
  3. When you pan the map, the tiles get clipped to the original extent. This is the same issue as number 1: Leaflet pans the map by transforming the map pane, and html2canvas is clipping the tiles before that transform is applied. To fix, add the transform X and Y values to the left and top values of each img, then set their transforms to 0.
  4. When you pan the map, html2canvas draws the tiles on top of the marker and line so they're no longer visible. This is fixed when you make the changes in number 3, but I don't understand why. Maybe the drawing order changes when an element's properties are updated?

Here's the fixed version: http://jsfiddle.net/oo2yms1h/

There's still one issue I haven't worked out yet: when you pan the map so the line is near the right edge, it doesn't show up on the canvas. But I'm guessing it's another result of html2canvas clipping before the transform.

One more note: The default leaflet marker is an image hosted from cdn.leaflet.com which gives a CORS error, meaning it won't show up on the canvas. Anyone using it with html2canvas would need to host the icon themselves, or switch to a different marker style.

JAPiasente, looks like my fix in number 3 can be adapted to resolve your tile and panning issues. Also, your markers are SVG paths and seem to be suffering from the same double-transform issue as my line in number 2, they're showing up too far to the upper left. Here's a version with those changes:

https://jsfiddle.net/6tn0zy48/

Everything shows up in the right place, except the markers are getting clipped further up than the bottom of the map. Looks like it's the same issue I'm still working on.

CraigVA commented 9 years ago

Fixing that last remaining problem wasn't hard, it was just a matter of setting the transform to 0 and using left and top instead.

My simple example, updated: http://jsfiddle.net/oo2yms1h/1/

And my revised fix for your map: https://jsfiddle.net/6tn0zy48/1/

CraigVA commented 9 years ago

I was working in Chrome, and I just noticed that my fix doesn't work in Firefox and IE. They were giving the transform styles as translate instead of translate3d, and my code assumed it would always be translate3d. And they positioned the tiles using translate instead of left and top like in Chrome. This updated example is revised for all three browsers. Tiles and marker icons work in all three, but I still can't get the SVG paths to show up in Firefox and IE.

http://jsfiddle.net/oo2yms1h/3/

CraigVA commented 9 years ago

IE needed html2canvas.svg before it would show the line, and it also doesn't work when transforms or left/top properties are set on the SVG layer. This example works in IE, and almost works in Firefox:

http://jsfiddle.net/oo2yms1h/5/

To get the line to show in firefox, you also need to switch to base64 encoding by making the change to html2canvas mentioned in #648: https://github.com/niklasvh/html2canvas/pull/648/files

JAPiasente commented 9 years ago

Thanks for all your help so far. The only last issue now is if I have an svg overlay ontop of it, IE isnt playing nicely with it. The map and the svg overlay are working nicely in chrome. Any ideas?

adam-hanna commented 8 years ago

@CraigVA You are amazing. I just spent the last two days trying to fix this issue. I wish I had come across your jsfiddles earlier. Thanks!!!

carsnwd commented 7 years ago

I had an issue using @CraigVA solution where if I pan the map at all, the overlays would be off center from the tiles.

To combat it, all I did was a redraw() function that set the view to some other place in the ocean for a second and then set the view back to where it was. Works fine.

function redraw() {
    var lat_tmp = map.getCenter().lat;
    var lng_tmp = map.getCenter().lng;
    map.setView([-66.22149259832975, -1.142578125]);
    setTimeout(function () {
        waitForTilesToLoad()
    }, 50000);
    map.setView([lat_tmp, lng_tmp]);
}

In case anyone comes across this and has the same issue.

xylolink commented 6 years ago

Hi, thanks for this hack, it works well except for Path layers and for big TileLayers.

thanks

niklasvh commented 6 years ago

Is this still an issue with v1.0.0? If so, could you please share an example on jsfiddle.

xylolink commented 6 years ago

I solved this problem using React : the leaflet component is reset after moving the map, keeping in state the last center and zoom level. There's no more conflict with html2canvas as the provided map is a new map.

xylolink commented 6 years ago

Ok, I made the update in the jsfiddle code here : http://jsfiddle.net/2zkLkLxc/ There is no more clipping problems with V1.0.0 but we have back problems with icons clipping (anytime) and tiles clipping when moving the map. I tried to remove the hacks on tiles and it doesn't change anything.

It's strange, because I have updated to v.1.0.0 in my code and I don't have icons clipping.

no-response[bot] commented 6 years ago

This issue has been automatically closed because there has been no response to our request for more information from the original author. With only the information that is currently in the issue, we don't have enough information to take action. Please reach out if you have or find the answers we need so that we can investigate further.

nileshpanchal973githubcom commented 6 years ago

I have the same problem, but I used Leaflet Map instead of Google Map. The code is below

var transform=$(".leaflet-map-pane").css("transform"); if (transform) {
var c = transform.split(","); var d = parseFloat(c[4]); var h = parseFloat(c[5]);
$(".leaflet-map-pane").css({
"transform": "none",
"left": d,
"top": h
}) }
html2canvas(document.body).then(function(canvas){
$(".leaflet-map-pane").css({
left: 0, top: 0,
"transform": transform
})
} // Here is used html2canvas 1.0.0-alpha.9

potemkin-git commented 6 years ago

@CraigVA Thanks a LOT!

tonywr71 commented 6 years ago

I have an issue at the moment up on stackoverflow regarding the clipping problem. I'm using leaflet 1.3.1. If someone can provide me some guidance, it would be much appreciated. The issue is here.

ghost commented 6 years ago

@niklasvh The issue is still there: http://jsfiddle.net/x3jzsg9b/4/

marianoeramirez commented 6 years ago

The issue its still there: https://jsfiddle.net/x512pgjt/269/

lombao14 commented 5 years ago

@amarandon @bomba1990

Try to remove de renderer padding from your map. It worked for me.

map.getRenderer(map).options.padding = 0;

dygos2 commented 4 years ago

This worked lovely!

html2canvas(document.querySelector("#mapid"), { allowTaint: true, useCORS: true }).then(canvas => { document.body.appendChild(canvas) });

ggaulard commented 3 years ago

In my case I needed to do this const map = document.querySelector(".leaflet-overlay-pane .leaflet-zoom-animated") as HTMLElement; const coordinates = map.style.transform.split("(")[1].split(")")[0].split(","); map.style.top = -1 parseInt(coordinates[1].replace("px", ""), 10) + "px"; map.style.left = -1 parseInt(coordinates[0].replace("px", ""), 10) + "px"; then remove the top, left padding.

JacobReynolds commented 2 years ago

For anyone still coming across this, I was able to solve this issue by using the preferCanvas option of leaflet.

RodoAmoroso commented 2 years ago

This worked lovely!

html2canvas(document.querySelector("#mapid"), { allowTaint: true, useCORS: true }).then(canvas => { document.body.appendChild(canvas) });

Thanks!!! It worked for me!

Elliott-Rose-BSC commented 2 years ago

For anyone still coming across this, I was able to solve this issue by using the preferCanvas option of leaflet.

This was the solution for me as well. After all of the complex calculations that I tried, simply adding this to the Dash Leaflet app that I am working on solved the SVG overlay perfectly, and without all of the extra functions and calculations. Just add preferCanvas in the dl.Map options, and you're set.

cybercris commented 1 year ago

And for removing the controls before taking the snapshot for html2canvas?

ghost-ng commented 8 months ago

after all this time, the issue is still here hahaha This fixes it https://github.com/niklasvh/html2canvas/issues/567#issuecomment-1118884370

var map = L.map('map', {
    preferCanvas: true // This ensures Leaflet renders vectors and geometries on a Canvas.
}).setView([51.505, -0.09], 4);