如何从浏览器中保存/导出用CSS样式化的内联SVG为图像文件

26
我有一个Web应用程序,它基于用户交互在客户端动态生成内联SVG图形。该图形部分由元素属性定义,部分由CSS类和ID定义。
我想提供一个选项让客户端将内联SVG保存为位图或.svg图像文件。重要的是,所有样式都从外部CSS样式文件中应用。如何使用JavaScript在浏览器中实现此功能并保存为.svg或位图(.gif),或者使用node.js在服务器上实现?
3个回答

23

为什么不复制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);
}

绝对的传奇!运行得非常好。 - Bogdan Kiselitsa
备选复制:https://dev59.com/3G865IYBdhLWcg3wHK3u#44769098 - hsc
我希望它更通用一些:只需复制所有样式,所以我将 else if (TagName in RelevantStyles) 改为了 else if (TagName),并在 else if 体中添加了递归作为最后一行。 - Harry

9

在保存SVG元素之前,您需要明确将计算出的CSS样式设置为每个SVG元素的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>

2
那个 traverse 函数似乎只能深入一层,对我来说没有任何用处。使用 jQuery,以下代码行可以获得所有准备运行 explicitlySetStyle 的元素 $('#svg').find("*"); - Craig

3
如果你的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
这段代码运行得非常好!这正是我想要的。非常感谢! - toshi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接