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

Capture SVG #95

Closed
trongdau184 opened this issue Apr 26, 2012 · 65 comments
Closed

Capture SVG #95

trongdau184 opened this issue Apr 26, 2012 · 65 comments
Labels

Comments

@trongdau184
Copy link

Hello,

Does html2canvas support capture SVG, if yes, how to do that? Could you give an example?

The example in tests\images didn't capture SVG.

Please help. Thanks a lot.

@niklasvh
Copy link
Owner

No, it does not. It isn't a fault in the library, but a bug in most browsers (latest stable Firefox, version 12, has this issue fixed and is the only one where it works as expected afaik). The problem is that any SVG image taints the canvas, making it unreadable. By default, html2canvas ignores content which taints the image, as we want to keep the canvas readable (i.e. for example if you want to use toDataUrl).

If however, you don't care that SVG images taint the canvas, then you can set the option allowTaint to true and SVG's should render correctly for browsers that allow inline SVG.

I'd also like to point out, if SVG rendering would work correctly cross browser, this library would become almost obsolete, as SVG's can be used to render HTML. I've committed already some code which accelerates the canvas rendering significantly on browsers where SVG rendering works (i.e. FF12), as well as allowing support for all CSS properties the browser supports. For information have a look at https://github.com/niklasvh/html2canvas/blob/master/src/Parse.js#L1227

@trongdau184
Copy link
Author

Hello,
Thanks a lot for your quickly response.
I tried as your suggestions: set allowTaint to true as below (modify test.js):

setTimeout(function() {

        $(h2cSelector).html2canvas($.extend({
            flashcanvas: "../external/flashcanvas.min.js",
            logging: true,
            profile: true,
            useCORS: true,
            allowTaint: true
        }, h2cOptions));
    }, 100);

It's render SVG successfully in example images.html.

But in case the SVG is more complicated it doesn't render correctly in cross browser. For example: I would like to capture the chart (http://www.highcharts.com/demo/), in Chrome: it just capture the x axis and y axis of chart when chart is rendered as inline SVG, in case chart render as img with source from extenal svg file, it doesn't capture anything.
Contrary tọ Chrome, in Firefox, it doesn't capture anything when chart render as inline SVG, but can capture chart succesfully when chart is an image from external svg file. The worst is in IE9, nothing is captured.

Do you have any idea to solve this issue?

@niklasvh
Copy link
Owner

niklasvh commented May 1, 2012

Using as image should work for FF/Chrome. What you could try is whether a simple canvas render using drawImage works with the SVG.

@joeyrobert
Copy link

+1 for this. I have the same use case as babyhero, I need to grab a screenshot of highcharts SVG graphs for a reporting tool. It might be possible to grab all the highcharts SVGs individually, but it's rendered within a view with other information that we would like to have "Save to Screenshot" capability for.

@joeyrobert
Copy link

@babyhero184 I got around this limitation in html2canvas by using canvg (http://code.google.com/p/canvg/) to convert highcharts SVG's to PNG's on the fly, hiding the SVG elements and showing the PNG. Then I took a screenshot on the fly with html2canvas and then hid the PNG and showed the highcharts SVG again. It's a roundabout process (SVG -> canvas -> PNG -> canvas -> PNG) but it works for my needs.

@Jagdeep1
Copy link

@joeyrobert I am also facing the same issue with highcharts and html2canvas. It will be a great help if you can share gist of your code.
Thanks in advance.

@kangax
Copy link

kangax commented Aug 16, 2013

@joeyrobert @Jagdeep1 another option is using Fabric.js to render SVG on canvas, then retrieve its image.

@outaTiME
Copy link

+1

2 similar comments
@mbritton
Copy link

+1

@jjmontesl
Copy link

+1

@majuansari
Copy link

any solution for this issue ?

@brcontainer
Copy link
Contributor

@majuansari SVG to Canvas and Canvas to image works in firefox, but in Chrome there is a security lock.

@mbritton
Copy link

I was using jQuery to capture chart elements, and discovered the Highcharts SVG wasn't properly parsed when did so. If anyone's passing a jQuery selector of a chart, it may not contain the SVG.

@brcontainer
Copy link
Contributor

@mbritton try http://code.google.com/p/canvg/ (foreignObject not work)

@mbritton
Copy link

Yes, that was my solution.

@pawsong
Copy link

pawsong commented Feb 7, 2014

I solved this issue by replacing svg elements with canvg-generated canvas elements while capturing.
Here is my code.

// targetElem is a jquery obj
function take (targetElem) {
    var nodesToRecover = [];
    var nodesToRemove = [];

    var svgElem = targetElem.find('svg');

    svgElem.each(function(index, node) {
        var parentNode = node.parentNode;
        var svg = parentNode.innerHTML;

        var canvas = document.createElement('canvas');

        canvg(canvas, svg);

        nodesToRecover.push({
            parent: parentNode,
            child: node
        });
        parentNode.removeChild(node);

        nodesToRemove.push({
            parent: parentNode,
            child: canvas
        });

        parentNode.appendChild(canvas);
    });

    html2canvas(targetElem, {
        onrendered: function(canvas) {
            var ctx = canvas.getContext('2d');
            ctx.webkitImageSmoothingEnabled = false;
            ctx.mozImageSmoothingEnabled = false;
            ctx.imageSmoothingEnabled = false;

            canvas.toBlob(function(blob) {
                nodesToRemove.forEach(function(pair) {
                    pair.parent.removeChild(pair.child);
                });

                nodesToRecover.forEach(function(pair) {
                    pair.parent.appendChild(pair.child);
                });
                saveAs(blob, 'screenshot_'+ moment().format('YYYYMMDD_HHmmss')+'.png');
            });
        }
    });
}

@steren
Copy link

steren commented Mar 31, 2014

Just a quick note:
I am sure canvg is working fine, but if you simply want to convert SVG to canvas, you can use the simple technique described here that loads the svg in an image and then get the image content as dataURL.

var image = new Image();
var xml = '<svg xmlns=....></svg>';
image.src = 'data:image/svg+xml,' + escape(xml); 
document.getElementById('container').appendChild(image);
image.onload = function() {
    image.onload = function() {};
    var canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;
    var context = canvas.getContext('2d');
    context.drawImage(image, 0, 0);
    image.src = canvas.toDataURL();
}

@xJREB
Copy link

xJREB commented May 6, 2014

@steren: While this works great in Chrome, is there a workaround for Firefox, which produces a NS_ERROR_NOT_AVAILABLE on the line context.drawImage(image, 0, 0) - apparently a know bug - or IE 11 where a SecurityError appears on canvas.toDataURL() despite the image being on the same domain? Haven't been able to get it running in other browsers besides Chrome...

@muhammedbasilsk
Copy link

@gifarangw your code works fine with highcharts. Thanks for your wonderful code. 👍 Only thing I removed was the canvas.toBlob part.

@remiremi
Copy link

remiremi commented Jun 4, 2014

@steren thanks for your solution, it works well (tested on latest chrome and firefox), and it doesn't need an additional library which is good. I made a few changes and combined with the code from @gifarangw and I obtained:

    function take(targetElem) {
        // First render all SVGs to canvases
        var elements = targetElem.find('svg').map(function() {
            var svg = $(this);
            var canvas = $('<canvas></canvas>');
            svg.replaceWith(canvas);

            // Get the raw SVG string and curate it
            var content = svg.wrap('<p></p>').parent().html();
            content = content.replace(/xlink:title="hide\/show"/g, "");
            content = encodeURIComponent(content);
            svg.unwrap();

            // Create an image from the svg
            var image = new Image();
            image.src = 'data:image/svg+xml,' + content;
            image.onload = function() {
                canvas[0].width = image.width;
                canvas[0].height = image.height;

                // Render the image to the canvas
                var context = canvas[0].getContext('2d');
                context.drawImage(image, 0, 0);
            };
            return {
                svg: svg,
                canvas: canvas
            };
        });
        targetElem.imagesLoaded(function() {
            // At this point the container has no SVG, it only has HTML and Canvases.
            html2canvas(targetElem[0], {
                onrendered: function(canvas) {
                    // Put the SVGs back in place
                    elements.each(function() {
                        this.canvas.replaceWith(this.svg);
                    });

                    // Do something with the canvas, for example put it at the bottom
                 $(canvas).appendTo('body');
                }
            })
        })
    }

(uses https://github.com/desandro/imagesloaded)

@fbotti
Copy link

fbotti commented Jun 18, 2014

Hi all just to say that the @remiremi solution worked great for me! I have a page with lots of graphs created with HighStocks in svg and this solution worked great! Well done guys!

@guypaskar
Copy link

@remiremi @fbotti , For some reason I don't get into img.onLoad thus, the graphs disappears (not being replaced with the canvas)

I'm working with amcharts. Any idea?

@remiremi
Copy link

remiremi commented Jul 9, 2014

@guypaskar your svg could need to be curated further. You can add $(image).appendTo('body'); to the code before seting image.onload, then in Firefox you'll see an icon for the invalid image, you can right click > view image and see the error.

I gave a quick try to an amcharts svg and I had to add the properties xmlns="http://www.w3.org/2000/svg" width="1400" height="500" to the svg tag to make it work. (The height and width were specified as css properties, but for some reason it didn't get picked up.)

@zackskeeter
Copy link

I have found a simple fix.

Make sure your svg image has a width and height.

There are not enough arguments otherwise because an svg cant be measured like a image can.

@usmonster
Copy link
Contributor

Can someone on this issue recap? Is the original issue resolved? It seems that certain browser issues that may have caused the majority of problems mentioned here have been fixed since 2012. According to some comments here, the use of other libraries such as canvg is also no longer needed?

For remaining issues, would they be covered by #197 and/or #267? Can this one be closed?

@niklasvh
Copy link
Owner

niklasvh commented Sep 1, 2014

It's still an issue with at least some browsers

@sohel-ahmed-ansari
Copy link

@jeremyk
Copy link

jeremyk commented Sep 9, 2016

I tried this using angular but end up with a tainted canvas. I guess I need the raw SVG?

<img svg-2-canvas-src="/assets/your.svg"/>

angular.module('APP')
    .directive('svg2CanvasSrc', function() {
        return {
            restrict: 'A',
            scope: false,
            link: function($scope, $element, $attrs) {

                var svgSrc = $attrs['svg2CanvasSrc'];

                var image = new Image();

                image.onload = function() {
                    image.onload = function() {};
                    var canvas = document.createElement('canvas');
                    canvas.width = image.width;
                    canvas.height = image.height;
                    var context = canvas.getContext('2d');
                    context.drawImage(image, 0, 0);
                    $element[0].src = canvas.toDataURL();
                };

                image.src = svgSrc;
            }
        };
    });

@uragecz
Copy link

uragecz commented Dec 13, 2016

@remiremi i used your code but result is same, svg is missing..
original - https://s29.postimg.org/5lkprhx93/with_SVG.png
result - https://s23.postimg.org/j1otj2por/without_SVG.png

@EslamAbdelnasser
Copy link

@remiremi ... what is the plugin name which i have attache when um using this script

@solutionstack
Copy link

Basically, setting an explicit width/height on the SVG just before calling html2canvas fixes most cases for me..
What i do is loop through the various SVG elements of interests, get their browser calculated width/height using .getBoundingClientRect() and set the width height property on the element, i noticed when SVG's have percentage and similar width/height set, it doesn't render, but with pixels, Booyah

@solutionstack
Copy link

Still having issues rendering multi-line span's and div's though

@vincent-pli
Copy link

Hi guys,
I resolve this issue(lost svg with firefox) by change the source code a little.
Firstly, the SVG element must have height and width attribute, no matter with pixels or not.
then you should change the source code from:
self.image.src = "data:image/svg+xml," + (new XMLSerializer()).serializeToString(node);
to:
self.image.src = "data:image/svg+xml," + encodeURIComponent((new XMLSerializer()).serializeToString(node));

then enjoy it.

@bernardbaker
Copy link

Does anyone have an idea of how I would call html2canvas on an SVG element in the DOM?

An example would be great!

@jpatel3
Copy link

jpatel3 commented Oct 27, 2017

+1 still an issue for capturing SVG element screenshot in firefox.

@iansawyerva
Copy link

iansawyerva commented Feb 11, 2018

{
  profile: true,
  allowTaint: false,
  onrendered: function() {
    //...done()
  }
}

@ris0
Copy link

ris0 commented Apr 27, 2018

Hey, I was able to resolve this issue using this solution (without jQuery):
Solution

@dev-amitkmishra
Copy link

let targetElem = document.getElementById('body');
let nodesToRecover = [];
let nodesToRemove = [];

    let svgElem = targetElem.querySelector('svg')

    let parentNode = svgElem.parentNode;
        // let svg = parentNode.innerHTML;
        let svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" id=\"svg-annotations\" style=\"opacity: 1;\" _ngcontent-c7=\"\">\n\t\t\t<defs><marker id=\"arrow-start\" refY=\"4.5\" markerWidth=\"9.9\" markerHeight=\"9\" orient=\"auto\"><path fill=\"black\" d=\"M 9.9 0 L 9.9 9 L 0 4.5 Z\" /></marker><marker id=\"arrow-end\" refX=\"9.9\" refY=\"4.5\" markerWidth=\"9.9\" markerHeight=\"9\" orient=\"auto\"><path fill=\"black\" d=\"M 0 0 L 0 9 L 9.9 4.5 Z\" /></marker></defs><g class=\"annotation-group\" id=\"measurement-36122\" style=\"opacity: 1;\"><g class=\"annotations\"><g class=\"annotation callout elbow editable \" transform=\"translate(758.541 408.978)\"><g class=\"annotation-connector\"><path class=\"connector\" fill=\"none\" stroke=\"black\" d=\"M 0 0 L 137 137 L 162 137\" /></g><g class=\"annotation-note\" transform=\"translate(162 137)\"><g class=\"annotation-note-content\" transform=\"translate(0 3)\"><rect class=\"annotation-note-bg\" fill=\"white\" fill-opacity=\"0\" x=\"0\" width=\"104.79\" height=\"41.36\" /><text class=\"annotation-note-label\" fill=\"black\" y=\"41.36\" dx=\"0\"><tspan x=\"0\" dy=\"0.8em\" /></text><text class=\"annotation-note-title\" font-weight=\"bold\" fill=\"black\"><tspan x=\"0\" dy=\"0.8em\">Face</tspan><tspan x=\"0\" dy=\"1.2em\">:5453831.5mm²</tspan></text></g><path class=\"note-line\" stroke=\"black\" d=\"M 0 0 L 104.79 0\" /><circle class=\"handle \" cursor=\"move\" fill=\"grey\" fill-opacity=\"0.1\" stroke=\"grey\" stroke-dasharray=\"5\" cx=\"0\" cy=\"0\" r=\"10\" /></g></g></g></g><g class=\"annotation-group\" id=\"measurement-59622\" style=\"opacity: 1;\"><g class=\"annotations\"><g class=\"annotation callout elbow editable \" transform=\"translate(889.656 387.507)\"><g class=\"annotation-connector\"><path class=\"connector\" fill=\"none\" stroke=\"black\" d=\"M 0 0 L 137 137 L 162 137\" /></g><g class=\"annotation-note\" transform=\"translate(162 137)\"><g class=\"annotation-note-content\" transform=\"translate(0 3)\"><rect class=\"annotation-note-bg\" fill=\"white\" fill-opacity=\"0\" x=\"0\" width=\"104.79\" height=\"41.36\" /><text class=\"annotation-note-label\" fill=\"black\" y=\"41.36\" dx=\"0\"><tspan x=\"0\" dy=\"0.8em\" /></text><text class=\"annotation-note-title\" font-weight=\"bold\" fill=\"black\"><tspan x=\"0\" dy=\"0.8em\">Face</tspan><tspan x=\"0\" dy=\"1.2em\">:5453831.5mm²</tspan></text></g><path class=\"note-line\" stroke=\"black\" d=\"M 0 0 L 104.79 0\" /><circle class=\"handle \" cursor=\"move\" fill=\"grey\" fill-opacity=\"0.1\" stroke=\"grey\" stroke-dasharray=\"5\" cx=\"0\" cy=\"0\" r=\"10\" /></g></g></g></g></svg>";

        this.canvas = document.createElement('canvas');
        this.canvas.setAttribute('id', '_canvas');

        canvg(this.canvas, svg, {renderCallback: function(){
            console.log(this);
        }});

        nodesToRecover.push({
            parent: parentNode,
            child: svgElem
        });
        parentNode.removeChild(svgElem);

        nodesToRemove.push({
            parent: parentNode,
            child: this.canvas
        });

        parentNode.appendChild(this.canvas);
        let data = this.canvas.toDataURL('image/png', 0.5);
        console.log(data);

I am doing this, but I a, getting only blank image in IE11. But, I can see the canvas on webpage. Can anyone please help me what I am doing wrong here?

@maestro888
Copy link

maestro888 commented Mar 14, 2019

Hello. I have a problem render svg when this have <image> tag.
barchart (24)

Sample svg

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/>
</svg>

Sample in jsfiddle http://jsfiddle.net/maestro888/fmjywksq/

@caneta
Copy link

caneta commented Mar 27, 2019

I have found a simple fix.

Make sure your svg image has a width and height.

There are not enough arguments otherwise because an svg cant be measured like a image can.

This is true but not for IE11, unfortunately...

@bernardbaker
Copy link

bernardbaker commented Mar 27, 2019 via email

@caneta
Copy link

caneta commented Mar 28, 2019

I've found a solution with canvg, reported here on stack overflow: it works perfectly on IE11 !

My re-elaborated snippet, removing jQuery dependency:

let svgIcons = document.querySelectorAll('.svg-icon');

for (let i = 0; i < svgIcons.length; ++i) {
  let curSvg = svgIcons[i];
  let canvas;
  let xml;

  // Size and color needed for every browser
  curSvg.setAttribute('width', curSvg.parentNode.offsetWidth);
  curSvg.setAttribute('height', curSvg.parentNode.offsetHeight);
  curSvg.setAttribute('style', 'fill: blue');

  canvas = document.createElement('canvas');
  canvas.className = 'screenShotTempCanvas';
  xml = new XMLSerializer().serializeToString(curSvg);
  canvg(canvas, xml);
  curSvg.parentNode.insertBefore(canvas, curSvg.nextSibling);
  curSvg.style.display = 'none';
 }

@bernardbaker
Copy link

bernardbaker commented Mar 28, 2019 via email

@Richard-biz
Copy link

Richard-biz commented Apr 10, 2019

Hi @niklasvh Nice to see your html2canvas javascript library ,Its a awesome library ,But Still Am facing the same issue for rendering the svg into canvas creation using npm version "1.0.0-alpha.12
" as well as "1.0.0-rc.1" version also in our anguar6 application, As per your response from the issue list, The issue will come svg doesn't contain the height and width , But in my case my Svg have 100% height and width and nested div contains svg tag also and nested existing canvas also , I can export my page as a png image but the image contains only the crashed svg icons and existing canvas also not rendering . And one more thing once export done my another page svg all are showing as crashed images. for your reference i attached my screen shot here please have a look once and let me know thanks.
html2canvas(this.screen.nativeElement,{useCORS: true}).then(canvas => {
this.canvas.nativeElement.src = canvas.toDataURL('image/png');
this.downloadLink.nativeElement.href = canvas.toDataURL('image/png');
this.downloadLink.nativeElement.download = 'screenshot.png';
this.downloadLink.nativeElement.click();
});

html2canvas_orginal_Before export
html2canvas_orginal_after export
html2canvas_exported image

@sunildipun
Copy link

testpdf (1).pdf
Hi,
I am getting the axis but the rest of the area of chart is black. Any suggestion

@angleneo
Copy link

Hi @niklasvh Nice to see your html2canvas javascript library ,Its a awesome library ,But Still Am facing the same issue for rendering the svg into canvas creation using npm version "1.0.0-alpha.12
" as well as "1.0.0-rc.1" version also in our anguar6 application, As per your response from the issue list, The issue will come svg doesn't contain the height and width , But in my case my Svg have 100% height and width and nested div contains svg tag also and nested existing canvas also , I can export my page as a png image but the image contains only the crashed svg icons and existing canvas also not rendering . And one more thing once export done my another page svg all are showing as crashed images. for your reference i attached my screen shot here please have a look once and let me know thanks.
html2canvas(this.screen.nativeElement,{useCORS: true}).then(canvas => {
this.canvas.nativeElement.src = canvas.toDataURL('image/png');
this.downloadLink.nativeElement.href = canvas.toDataURL('image/png');
this.downloadLink.nativeElement.download = 'screenshot.png';
this.downloadLink.nativeElement.click();
});

html2canvas_orginal_Before export
html2canvas_orginal_after export
html2canvas_exported image

the same problem,Error finding the svg in the cloned document

@sasirekaK
Copy link

Before using html2canvas i am temporarily setting height and width to the SVGs. This way i am able to capture the svg.

var selectedItem = document.querySelector(".chartWrapper");
var svgElements = selectedItem.querySelectorAll('svg');
for (let i = 0; i < svgElements.length; i++) {
selectedItem.getElementsByTagName("svg")[i].style.height = document.getElementsByTagName("svg")[i].getBoundingClientRect().height;
selectedItem.getElementsByTagName("svg")[i].style.width = document.getElementsByTagName("svg")[i].getBoundingClientRect().width;
}
html2canvas(selectedItem, {backgroundColor: '#000',allowTaint: true}).then(canvas => {
var myImage = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
window.location.href = myImage;
})
for (let i = 0; i < svgCount; i++) {
selectedItem.getElementsByTagName("svg")[i].style.height = '100%';
selectedItem.getElementsByTagName("svg")[i].style.width = '100%';
}

@wogaozhixu
Copy link

wogaozhixu commented Sep 21, 2020

// use html2canvas in react project when i need a screenshot for the svg
/**
 * svg to canvas
 * @param targetElem
 * @return {Promise<[]>} // return svg elements
 */
async function svgToCanvas(targetElem) {
  let nodesToRecover = [], nodesToRemove = [], svgElems = targetElem.querySelectorAll('svg');

  for (let i = 0; i < svgElems.length; i++) {
    let node = svgElems[i], parentNode = node.parentNode, svg = parentNode.innerHTML,
      canvas = document.createElement('canvas'),
      ctx = canvas.getContext('2d');
    canvas.setAttribute('data-cover', 'svg');
    const v = await Canvg.from(ctx, svg);
    v.start();
    // record svg elements
    nodesToRecover.push({
      parent: parentNode,
      child: node
    });
    parentNode.removeChild(node);
    //  record canvas elements
    nodesToRemove.push({
      parent: parentNode,
      child: canvas
    });

    parentNode.appendChild(canvas);
  }
  return nodesToRecover;
}

/**
 * recover canvas to svg
 * @param targetElem
 * @param svgElems // need to recover svg elements
 * @return *
 */
function recoverCanvasToSvg(targetElem, svgElems) {
  let canvasElems = targetElem.querySelectorAll("canvas[data-cover='svg']");
  if(!targetElem){
    throw new Error('must have targetElem param')
  }
  if(!isArray(svgElems) && svgElems.length !== canvasElems.length) {
    throw new Error('svgElems must be an Array, and it`s length equal to canvasElems`s length')
  }
  for(let i = 0; i < canvasElems.length; i++){
    let node = canvasElems[i], parentNode = node.parentNode;
    parentNode.removeChild(node);
    parentNode.appendChild(svgElems[i]?.child);
  }
}

@vijitverve
Copy link

The simplest way is to put an SVG tag in the div tag. Don't apply or write any CSS in the SVG tag. Instead, apply all the CSS properties to that div tag.
eg:
Instead of writing this <svg ... styles="top: 50%"/> -> <div styles="top: 50%"><svg.../></div>

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

No branches or pull requests