如何将带有css样式的内联SVG从浏览器保存/导出到图像文件
Posted
技术标签:
【中文标题】如何将带有css样式的内联SVG从浏览器保存/导出到图像文件【英文标题】:how to save/ export inline SVG styled with css from browser to image file 【发布时间】:2013-03-03 01:26:14 【问题描述】:我有一个 Web 应用程序,它根据用户交互在客户端动态生成内联 SVG 图形。图形部分由元素属性定义,部分由 CSS 类和 id 定义。
我希望能够为客户端提供一个选项,将内联 SVG 的副本保存为位图或 .svg 图像文件。从外部 css 样式文件应用所有样式很重要。 如何提供此功能以保存为 .svg 或位图 (.gif),最好在浏览器中使用 javascript 或在服务器上使用 node.js 保存?
【问题讨论】:
【参考方案1】:为什么不复制 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);
【讨论】:
绝对传奇!效果很好。 替代复制:***.com/a/44769098/3559690 我希望它更通用一点:只需复制所有样式,所以我将 else if (TagName in RelevantStyles) 更改为 else if (TagName) 并且我在最后一行添加了一个递归else if 正文。【参考方案2】:在保存之前,您需要将计算出的 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" >
<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" />
<br/>
image original:
<canvas id="canvasOriginal" ></canvas>
<br/>
image computed:
<canvas id="canvasComputed" ></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>
【讨论】:
那个遍历函数似乎只深入了一层,这对我没有任何用处。使用 jQuery,以下行让所有元素准备好通过explicitlySetStyle
$('#svg').find("*");
运行【参考方案3】:
如果你的css规则不是太复杂,可以按照以下步骤进行:
读取 .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');
现在将样式应用于所有 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 + '');
);
);
);
使用canvg进行转换
$('body').after('<canvas id="sm_canvas" style="display=none;"></canvas>');
var canvas = document.getElementById('sm_canvas');
canvg(canvas, $("<div>").append($('svg').clone()).html());
从画布中获取图像
var imgData = canvas.toDataURL('image/jpeg');
【讨论】:
这段代码工作得很好!这正是我想要的。非常感谢!以上是关于如何将带有css样式的内联SVG从浏览器保存/导出到图像文件的主要内容,如果未能解决你的问题,请参考以下文章
inline svg - 如何将浏览器默认样式应用于foreignObject中的xhtml?
Webpack 从我的样式表中的内联 SVG 中去除标签,我不知道为啥