Search code examples
javascriptsvgcanvasexportpng

How do I get the full canvas content (even the hidden ones) with Javascript?


I am currently working on a Javascript project and I am struggling with exporting the entire SVG image on the canvas. So far I've been only able to export the visible part of the canvas, with out the "hidden" parts.

How do I capture the full canvas content? Is there a way to do it without messing around with the original canvas size?

I am using D3.js V3

enter image description here Screenshot of my project

Here's my code:

    var svgString;
    window.onload = function(){
    setTimeout(function() {

        exportSVG = document.getElementById("canvas");

        document.getElementById("canvas").style.fontFamily= "lato";
        document.getElementById("canvas").style.width= exportSVG.getBBox().width * 1;
        document.getElementById("canvas").style.height= exportSVG.getBBox().height * 1;

        svgString = getSVGString(exportSVG);
        console.log(exportSVG.getBBox().width + " / " + exportSVG.getBBox().height);
        svgString2Image(svgString, exportSVG.getBBox().width, exportSVG.getBBox().height, 'png', save); // passes Blob and filesize String to the callback
        console.log("svg export code loaded");
        // console.log(svgString.getBBox().width); document.getElementById("canvas").getBBox().width
    }, 5000);
};

function save(dataBlob, filesize) {
    saveAs(dataBlob, 'D3 vis exported to PNG.png'); // FileSaver.js function
}

// Below are the functions that handle actual exporting:
// getSVGString ( svgNode ) and svgString2Image( svgString, width, height, format, callback )
function getSVGString(svgNode) {
    svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
    var cssStyleText = getCSSStyles(svgNode);
    appendCSS(cssStyleText, svgNode);

    var serializer = new XMLSerializer();
    var svgString = serializer.serializeToString(svgNode);
    svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
    svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix

    return svgString;

    function getCSSStyles(parentElement) {
        var selectorTextArr = [];

        // Add Parent element Id and Classes to the list
        selectorTextArr.push('#' + parentElement.id);
        for (var c = 0; c < parentElement.classList.length; c++)
            if (!contains('.' + parentElement.classList[c], selectorTextArr))
                selectorTextArr.push('.' + parentElement.classList[c]);

        // Add Children element Ids and Classes to the list
        var nodes = parentElement.getElementsByTagName("*");
        for (var i = 0; i < nodes.length; i++) {
            var id = nodes[i].id;
            if (!contains('#' + id, selectorTextArr))
                selectorTextArr.push('#' + id);

            var classes = nodes[i].classList;
            for (var c = 0; c < classes.length; c++)
                if (!contains('.' + classes[c], selectorTextArr))
                    selectorTextArr.push('.' + classes[c]);
        }

        // Extract CSS Rules
        var extractedCSSText = "";
        for (var i = 0; i < document.styleSheets.length; i++) {
            var s = document.styleSheets[i];

            try {
                if (!s.cssRules) continue;
            } catch (e) {
                if (e.name !== 'SecurityError') throw e; // for Firefox
                continue;
            }

            var cssRules = s.cssRules;
            for (var r = 0; r < cssRules.length; r++) {
                if (contains(cssRules[r].selectorText, selectorTextArr))
                    extractedCSSText += cssRules[r].cssText;
            }
        }

        return extractedCSSText;
        function contains(str, arr) {
            return arr.indexOf(str) === -1 ? false : true;
        }
    }

    function appendCSS(cssText, element) {
        var styleElement = document.createElement("style");
        styleElement.setAttribute("type", "text/css");
        styleElement.innerHTML = cssText;
        var refNode = element.hasChildNodes() ? element.children[0] : null;
        element.insertBefore(styleElement, refNode);
    }
}

function svgString2Image(svgString, width, height, format, callback) {
    var format = format ? format : 'png';

    var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString))); // Convert SVG string to data URL

    var canvas = document.createElement("canvas");
    var context = canvas.getContext("2d");

    canvas.width = width;
    canvas.height = height;

    var image = new Image();
    image.onload = function() {
        context.clearRect(0, 0, width, height);
        context.drawImage(image, 0, 0, width, height);

        canvas.toBlob(function(blob) {
            var filesize = Math.round(blob.length / 1024) + ' KB';
            if (callback) callback(blob, filesize);
        });


    };

    image.src = imgsrc;
}

Solution

  • Simply change your <svg> viewBox attribute before you serialize it to a string so that it displays everything:

    var svg = document.querySelector('svg');
    var toExport = svg.cloneNode(true); // avoids having to reset everything afterward
    // grab its inner content BoundingBox
    var bb = svg.getBBox();
    // update its viewBox so it displays all its inner content
    toExport.setAttribute('viewBox', bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height);
    toExport.setAttribute('width', bb.width);
    toExport.setAttribute('height', bb.height);
    
    var svgAsStr = new XMLSerializer().serializeToString(toExport);
    var blob = new Blob([svgAsStr], {type: 'image/svg+xml'});
    var img = new Image();
    img.onload = drawToCanvas;
    img.src = URL.createObjectURL(blob);
    
    function drawToCanvas(evt) {
      var canvas = document.createElement('canvas');
      canvas.width = this.width;
      canvas.height = this.height;
      canvas.getContext('2d').drawImage(this, 0,0);
      document.body.appendChild(canvas);
    }
    svg{border: 1px solid blue}
    canvas{border: 1px solid green}
    <svg width="50" height="50" viewBox="0 0 50 50">
      <rect x="0" y="0" width="200" height="50" fill="#CCC"/>
    </svg>