javascript 如何将带有 css 样式的内联 SVG 从浏览器保存/导出到图像文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15181452/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 23:47:12  来源:igfitidea点击:

how to save/ export inline SVG styled with css from browser to image file

javascriptimagebrowsersvgexport

提问by johowie

I have a web app that is generating inline SVG graphics in the client on the fly based on user interaction. The graphic is defined partly by element attributes and partially by CSS classes and id's.

我有一个 Web 应用程序,它根据用户交互在客户端动态生成内联 SVG 图形。图形部分由元素属性定义,部分由 CSS 类和 id 定义。

I would like to be able to provide an option for the client to save a copy of the inline SVG as either a bitmap or an .svg image file. It is important that all styles are applied from the external css styling files. How can I provide this functionality to save as either as .svg or bitmap (.gif) preferably in the browser using javascript or else on the server with node.js ?

我希望能够为客户端提供一个选项,将内联 SVG 的副本保存为位图或 .svg 图像文件。从外部 css 样式文件应用所有样式很重要。我如何提供此功能以保存为 .svg 或位图 (.gif) 最好在使用 javascript 的浏览器中或在带有 node.js 的服务器上保存?

回答by hsc

Why not copying the SVG node/tree and then take the styles, as defined per tag (You will need the original tree, as the copy may be without styles in case the element is part of a longer tree). This ensures that you are only copying those styles relevant as set in the CSS file. The export type could easily be set before sending the package to the blob

为什么不复制 SVG 节点/树,然后采用每个标签定义的样式(您将需要原始树,因为如果元素是较长树的一部分,副本可能没有样式)。这可确保您只复制 CSS 文件中设置的相关样式。在将包发送到 blob 之前,可以轻松设置导出类型

var ContainerElements = ["svg","g"];
var RelevantStyles = {"rect":["fill","stroke","stroke-width"],"path":["fill","stroke","stroke-width"],"circle":["fill","stroke","stroke-width"],"line":["stroke","stroke-width"],"text":["fill","font-size","text-anchor"],"polygon":["stroke","fill"]};


function read_Element(ParentNode, OrigData){
    var Children = ParentNode.childNodes;
    var OrigChildDat = OrigData.childNodes;     

    for (var cd = 0; cd < Children.length; cd++){
        var Child = Children[cd];

        var TagName = Child.tagName;
        if (ContainerElements.indexOf(TagName) != -1){
            read_Element(Child, OrigChildDat[cd])
        } else if (TagName in RelevantStyles){
            var StyleDef = window.getComputedStyle(OrigChildDat[cd]);

            var StyleString = "";
            for (var st = 0; st < RelevantStyles[TagName].length; st++){
                StyleString += RelevantStyles[TagName][st] + ":" + StyleDef.getPropertyValue(RelevantStyles[TagName][st]) + "; ";
            }

            Child.setAttribute("style",StyleString);
        }
    }

}

function export_StyledSVG(SVGElem){


    var oDOM = SVGElem.cloneNode(true)
    read_Element(oDOM, SVGElem)

    var data = new XMLSerializer().serializeToString(oDOM);
    var svg = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
    var url = URL.createObjectURL(svg);

    var link = document.createElement("a");
    link.setAttribute("target","_blank");
    var Text = document.createTextNode("Export");
    link.appendChild(Text);
    link.href=url;

    document.body.appendChild(link);
}

回答by Ilya Kharlamov

You will need to explicitly set the calculated css styles as SVG dom style properties for each SVG element before saving it. Here is an example:

在保存每个 SVG 元素之前,您需要将计算出的 css 样式显式设置为 SVG dom 样式属性。下面是一个例子:

<html>
    <body>
    <!-- in this example the inline svg has black backgroud-->
    <svg id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1" height="190">
        <polygon id="polygon" points="100,10 40,180 190,60 10,60 160,180" style="stroke:purple;stroke-width:5;">
    </svg>
    <style>
        /* the external svg style makes svg shape background red */
        polygon 
        {
            fill:red;
        }
    </style>
<svg id="emptysvg" xmlns="http://www.w3.org/2000/svg" version="1.1" height="2"/>
<br/>
image original:
<canvas id="canvasOriginal" height="190" width="190" ></canvas>
<br/>
image computed:
<canvas id="canvasComputed" height="190" width="190" ></canvas>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script type="text/javascript" src="http://canvg.googlecode.com/svn/trunk/rgbcolor.js"></script> 
<script type="text/javascript" src="http://canvg.googlecode.com/svn/trunk/StackBlur.js"></script>
<script type="text/javascript" src="http://canvg.googlecode.com/svn/trunk/canvg.js"></script> 
<script src="http://www.nihilogic.dk/labs/canvas2image/canvas2image.js"></script>
<script type="text/javascript">
var svg = $('#svg')[0];
var canvasOriginal = $('#canvasOriginal')[0];
var ctxOriginal = canvasOriginal.getContext('2d');
var canvasComputed=$('#canvasComputed')[0];
var ctxConverted=canvasComputed.getContext("2d");
// this saves the inline svg to canvas without external css
canvg('canvasOriginal', new XMLSerializer().serializeToString(svg));
// we need to calculate the difference between the empty svg and ours
var emptySvgDeclarationComputed = getComputedStyle($('#emptysvg')[0]);
function explicitlySetStyle (element) {
    var cSSStyleDeclarationComputed = getComputedStyle(element);
    var i, len, key, value;
    var computedStyleStr = "";
    for (i=0, len=cSSStyleDeclarationComputed.length; i<len; i++) {
        key=cSSStyleDeclarationComputed[i];
        value=cSSStyleDeclarationComputed.getPropertyValue(key);
        if (value!==emptySvgDeclarationComputed.getPropertyValue(key)) {
            computedStyleStr+=key+":"+value+";";
        }
    }
    element.setAttribute('style', computedStyleStr);
}
function traverse(obj){
    var tree = [];
    tree.push(obj);
    if (obj.hasChildNodes()) {
        var child = obj.firstChild;
        while (child) {
            if (child.nodeType === 1 && child.nodeName != 'SCRIPT'){
                tree.push(child);
            }
            child = child.nextSibling;
        }
    }
    return tree;
}
// hardcode computed css styles inside svg
var allElements = traverse(svg);
var i = allElements.length;
while (i--){
    explicitlySetStyle(allElements[i]);
}
// this saves the inline svg to canvas with computed styles
canvg('canvasComputed', new XMLSerializer().serializeToString(svg));
$("canvas").click(function (event) {
    Canvas2Image.saveAsPNG(event.target);
});
</script>
    </body>
</html>

回答by Souvik

If your css rules are not too much complicated, you can do the following steps:

如果你的css规则不是太复杂,可以按照以下步骤进行:

  1. Read the .css file, which contains all the css rule. If required, you can use a different css file and put it on the server, which you can only use for this purpose.

    function readTextFile(file) {
        var rawFile = new XMLHttpRequest();
        var allText = '';
        rawFile.open("GET", file, false);
        rawFile.onreadystatechange = function () {
            if(rawFile.readyState === 4) {
                if(rawFile.status === 200 || rawFile.status == 0) {
                    allText = rawFile.responseText;
                }
            }
        };
        rawFile.send(null);
        return allText;
    }
    
    var svg_style = readTextFile(base_url + '/css/svg_sm_dashboard.css');
    
  2. Now apply the style on all the svg elements

    var all_style = svg_style.replace(/\r?\n|\r/g,'').split('}');
    all_style.forEach(function(el) {
        if (el.trim() != '') {
            var full_rule_string = el.split('{');
            var selector = full_rule_string[0].trim();
            var all_rule = full_rule_string[1].split(';');
            all_rule.forEach(function (elem) {
                if (elem.trim() != '') {
                    var attr_value = elem.split(':');
                    //d3.selectAll(selector).style(attr_value[0].trim() + '', attr_value[1].trim() + '');
                    var prop = attr_value[0].trim();
                    var value = attr_value[1].trim();
    
                    d3.selectAll(selector).each(function(d, i){
                        if(!this.getAttribute(prop) && !this.style[prop]){
                            d3.select(this).style(prop + '', value + '');
                        }
                    });
                }
           });
       }
    });
    
  3. Use canvg to convert it

    $('body').after('<canvas id="sm_canvas" style="display=none;"></canvas>');
    var canvas = document.getElementById('sm_canvas');
    canvg(canvas, $("<div>").append($('svg').clone()).html());
    
  4. Get Image from the canvas

    var imgData = canvas.toDataURL('image/jpeg');
    
  1. 阅读 .css 文件,其中包含所有 css 规则。如果需要,您可以使用不同的 css 文件并将其放在服务器上,您只能将其用于此目的。

    function readTextFile(file) {
        var rawFile = new XMLHttpRequest();
        var allText = '';
        rawFile.open("GET", file, false);
        rawFile.onreadystatechange = function () {
            if(rawFile.readyState === 4) {
                if(rawFile.status === 200 || rawFile.status == 0) {
                    allText = rawFile.responseText;
                }
            }
        };
        rawFile.send(null);
        return allText;
    }
    
    var svg_style = readTextFile(base_url + '/css/svg_sm_dashboard.css');
    
  2. 现在将样式应用于所有 svg 元素

    var all_style = svg_style.replace(/\r?\n|\r/g,'').split('}');
    all_style.forEach(function(el) {
        if (el.trim() != '') {
            var full_rule_string = el.split('{');
            var selector = full_rule_string[0].trim();
            var all_rule = full_rule_string[1].split(';');
            all_rule.forEach(function (elem) {
                if (elem.trim() != '') {
                    var attr_value = elem.split(':');
                    //d3.selectAll(selector).style(attr_value[0].trim() + '', attr_value[1].trim() + '');
                    var prop = attr_value[0].trim();
                    var value = attr_value[1].trim();
    
                    d3.selectAll(selector).each(function(d, i){
                        if(!this.getAttribute(prop) && !this.style[prop]){
                            d3.select(this).style(prop + '', value + '');
                        }
                    });
                }
           });
       }
    });
    
  3. 使用 canvg 进行转换

    $('body').after('<canvas id="sm_canvas" style="display=none;"></canvas>');
    var canvas = document.getElementById('sm_canvas');
    canvg(canvas, $("<div>").append($('svg').clone()).html());
    
  4. 从画布中获取图像

    var imgData = canvas.toDataURL('image/jpeg');
    

回答by aturc

I think what is generally missing from these explanations on this topic, is the fact that a ".svg" file is actually just the markup in a text file.

我认为关于这个主题的这些解释通常缺少的是“.svg”文件实际上只是文本文件中的标记这一事实。

So get the svg contents from the dom, then save a text file with ".svg" filename.

所以从dom中获取svg内容,然后用“.svg”文件名保存一个文本文件。

var text = $('#svg-container').html();
text = text.slice(text.indexOf("<svg"),indexOf("/svg>")+4);
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', "image.svg");
pom.style.display = 'none';
document.body.appendChild(pom);
pom.click();
document.body.removeChild(pom);

If for example illustrator is giving you an error like "SVG invalid, validate svg before continuing". Then double check the contents of the downloaded file, and make sure there isn't any unnecessary s or anything, and that text.slice(text.indexOf("<svg"),indexOf("/svg>")+4);didn't slice off anything important.

例如,如果 illustrator 给您一个错误,例如“SVG 无效,请在继续之前验证 svg”。然后仔细检查下载文件的内容,确保没有任何不必要的 s 或任何东西,并且text.slice(text.indexOf("<svg"),indexOf("/svg>")+4);没有切掉任何重要的东西。