如何在使用D3.js创建SVG后保存/导出SVG文件(适用于IE、Safari和Chrome)?

102

我目前有一个使用D3的网站,我希望用户可以选择将SVG保存为SVG文件。我正在使用crowbar.js来完成这个操作,但它只在chrome上工作。在safari上什么也没发生,而IE则会在crowbar.js中使用click()方法下载文件时给出访问被拒绝的提示。

var e = document.createElement('script'); 

if (window.location.protocol === 'https:') { 
    e.setAttribute('src', 'https://raw.github.com/NYTimes/svg-crowbar/gh-pages/svg-crowbar.js'); 
} else { 
    e.setAttribute('src', 'http://nytimes.github.com/svg-crowbar/svg-crowbar.js'); 
}

e.setAttribute('class', 'svg-crowbar'); 
document.body.appendChild(e);

我该如何在Safari、IE和Chrome浏览器中,基于我的网站上的SVG元素下载SVG文件?

9个回答

101

这个方法一共分为5步骤,我通常使用它来输出内联SVG。

  1. 获取要输出的内联SVG元素。
  2. 通过XMLSerializer获取SVG源代码。
  3. 添加SVG和XLink的命名空间。
  4. 使用encodeURIComponent方法构建SVG的URL数据方案。
  5. 将此URL设置为某个“a”元素的href属性,并右键单击此链接以下载SVG文件。

//get svg element.
var svg = document.getElementById("svg");

//get svg source.
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg);

//add name spaces.
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
    source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
    source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}

//add xml declaration
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;

//convert svg source to URI data scheme.
var url = "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(source);

//set url value to a element's href attribute.
document.getElementById("link").href = url;
//you can download svg file by right click menu.

1
谢谢您的回答!这为我下载了一个svg,但是一切都变成黑色,颜色非常奇怪。这是为什么?您可以在我的网站上看到我所说的内容,我已经应用了您建议的代码:http://servers.binf.ku.dk/hemaexplorerbeta/ - 只需单击“提交”然后单击“导出图形”。非常感谢您。 - Sina Sohi
2
这个示例是一个简单的情况。如果您使用外部CSS文件通过链接元素使用CSS样式,则SVG和样式表的链接会中断。因此,通过将样式数据附加到内联SVG来解决此问题。 - defghi1977
1
所以,这个问题将通过将样式数据附加到内联SVG来解决。你能用另一种方式解释给我听吗?我不太理解。 - Sina Sohi
5
请参阅http://www.w3.org/TR/SVG11/styling.html#ReferencingExternalStyleSheets。因此,请添加<?xml-stylesheet href="xxx.css" type="text/css"?>并进行转换,但在这种情况下,SVG文件将不是独立的。 - defghi1977
9
注意:由于Chrome和Firefox现在在顶层使用SVG时都会阻止数据URI,因此这种方法已不再可行。 - Undistraction
显示剩余2条评论

92

我知道这个问题已经得到了回答,而且回答大多数情况下都有效。但是,我发现如果svg图像相对较大(约1MB),它会在Chrome上失败(但在Firefox上不会)。按照这里这里所述,如果您返回使用Blob构造,则可以解决该问题。唯一的区别是类型参数。在我的代码中,我想要一个单击按钮即可为用户下载svg,我通过以下方式实现:

var svgData = $("#figureSvg")[0].outerHTML;
var svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"});
var svgUrl = URL.createObjectURL(svgBlob);
var downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = "newesttree.svg";
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);

2019年10月编辑: 评论表明,即使不将downloadLink附加到document.body并在click()之后随后删除它,此代码也能够工作。我相信这曾经在Firefox上起作用,但现在不再起作用(Firefox要求您附加然后删除downloadLink)。该代码在Chrome上无论哪种方式都有效。


1
这在Chrome中非常有效!我会为这个简单的答案给出+100! - kashiraja
3
似乎在document.body中添加和删除downloadLink并不影响其功能。 - piotr_cz
1
.outerHTML 在Internet Explorer中不起作用。但是您可以像 defghi1977 一样使用 XMLSerializer()。其他所有内容保持不变。 - roland
@DaveTheScientist 如果SVG标签包含一些图片,则它们不会加载。 - Vadim Sheremetov
如果img元素指向外部图像文件,那么我相信这些链接会失效。我想你得找到一种方法将图像数据包含在svg中。 - DaveTheScientist
显示剩余3条评论

51

结合Dave和defghi1977的回答,这里有一个可重用的函数:

function saveSvg(svgEl, name) {
    svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    var svgData = svgEl.outerHTML;
    var preface = '<?xml version="1.0" standalone="no"?>\r\n';
    var svgBlob = new Blob([preface, svgData], {type:"image/svg+xml;charset=utf-8"});
    var svgUrl = URL.createObjectURL(svgBlob);
    var downloadLink = document.createElement("a");
    downloadLink.href = svgUrl;
    downloadLink.download = name;
    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);
}

调用示例:

saveSvg(svg, 'test.svg')

1
svgEl.outerHTML 在IE和Edge中无法工作。它们不支持SVG元素的outerHTML来源 - Dominus.Vobiscum
我想在服务器上静默保存SVG文件夹,而且我不想触发用户的下载,请问如何实现。谢谢! - Rehan
@senz,如果SVG包含图像,会怎么样?出于某种原因,这似乎不起作用。请检查我的示例 https://jsfiddle.net/10Ljgrv7/3/ 。 - Vadim Sheremetov

3
虽然这个问题已经有了答案,但我创建了一个名为 SaveSVG 的小型库,可以帮助保存使用外部样式表或外部定义文件(使用<use>def标签)的 D3.js 生成的 SVG。

3
我尝试了这里的每一个解决方案,但都没有解决我的问题。我的图片始终比我的d3.js画布小。
我不得不设置画布的宽度、高度,然后在上下文中进行clearRect操作才能使其正常工作。这是我的有效版本。
导出函数:
var svgHtml = document.getElementById("d3-canvas"),
    svgData = new XMLSerializer().serializeToString(svgHtml),
    svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"}),
    bounding = svgHtml.getBoundingClientRect(),
    width = bounding.width * 2,
    height = bounding.height * 2,
    canvas = document.createElement("canvas"),
    context = canvas.getContext("2d"),
    exportFileName = 'd3-graph-image.png';

//Set the canvas width and height before loading the new Image
canvas.width = width;
canvas.height = height;

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

    //Create blob and save if with FileSaver.js
    canvas.toBlob(function(blob) {
        saveAs(blob, exportFileName);
    });     
};
var svgUrl = URL.createObjectURL(svgBlob);
image.src = svgUrl;

它使用 FileSaver.js 保存文件。
这是我的画布创建,注意我在这里解决了命名空间问题。 d3.js 画布创建:
var canvas = d3.select("body")
    .insert("svg")
    .attr('id', 'd3-canvas')
    //Solve the namespace issue (xmlns and xlink)
    .attr("xmlns", "http://www.w3.org/2000/svg")
    .attr("xmlns:xlink", "http://www.w3.org/1999/xlink")
    .attr("width", width)
    .attr("height", height);

3

要使这段代码正常工作,您需要使用 FileSaver.js。

function save_as_svg(){


        var svg_data = document.getElementById("svg").innerHTML //put id of your svg element here

        var head = '<svg title="graph" version="1.1" xmlns="http://www.w3.org/2000/svg">'

        //if you have some additional styling like graph edges put them inside <style> tag

        var style = '<style>circle {cursor: pointer;stroke-width: 1.5px;}text {font: 10px arial;}path {stroke: DimGrey;stroke-width: 1.5px;}</style>'

        var full_svg = head +  style + svg_data + "</svg>"
        var blob = new Blob([full_svg], {type: "image/svg+xml"});  
        saveAs(blob, "graph.svg");


};

2

基于 @vasyl-vaskivskyi 的回答。

<script src="../../assets/FileSaver.js"></script>
<script>
function save_as_svg(){
    fetch('path/../assets/chart.css')
    .then(response => response.text())
    .then(text => {
        var svg_data = document.getElementById("svg").innerHTML
        var head = '<svg title="graph" version="1.1" xmlns="http://www.w3.org/2000/svg">'
        var style = "<style>" + text + "</style>"
        var full_svg = head +  style + svg_data + "</svg>"
        var blob = new Blob([full_svg], {type: "image/svg+xml"});  
        saveAs(blob, "graph.svg");
    })
};
save_as_svg();
</script>

上面的代码读取了你的chart.css文件,然后将css代码嵌入到你的svg文件中。

1

我尝试了这个方法,对我来说有效。

function downloadSvg() {

var svg = document.getElementById("svg");
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg);
source = source.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace

source = source.replace(/ns\d+:href/g, 'xlink:href'); // Safari NS namespace fix.


if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
    source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if (!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)) {
    source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}


var preface = '<?xml version="1.0" standalone="no"?>\r\n';
var svgBlob = new Blob([preface, source], { type: "image/svg+xml;charset=utf-8" });
var svgUrl = URL.createObjectURL(svgBlob);
var downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = name;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);

}


我曾经苦苦挣扎于下载SVG文件,尝试了很多方法,但最终这个解决方案奏效了。非常感谢Afshar。 - Jeremy Gillbert

0
在我的情况下,我必须在其他项目中使用一些使用D3.js创建的SVG图像。因此,我打开了开发工具并检查了这些SVG,并复制了它们的内容。然后创建了一个新的空白SVG文件,并将复制的内容粘贴到那里。然后我在其他区域中使用了这些新的SVG文件。
如果您想以编程方式执行此操作,那么我们可以使用document.getElementById('svgId')。
我知道这是一种基本方法,但如果有人觉得有用的话,请参考。

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