I tried to download the svg tag element, actually the image that the svg renders using the next function:
// Get the SVG element
const svg = document.getElementsByTagName('svg')[0];
const canvas = document.createElement('canvas');
canvas.width = svg.clientWidth;
canvas.height = svg.clientHeight;
const img = new Image();
img.src = `data:image/svg+xml;utf8,${new XMLSerializer().serializeToString(
svg,
)}`;
img.onload = function() {
console.log(img)
canvas.getContext('2d')?.drawImage(img, 0, 0);
};
const a = document.createElement('a');
a.download = 'my-image.png';
a.href = canvas.toDataURL();
a.click();
When i click on the dowsnload buttoon, the image is downloaded but it is black without any character. Why it is hapening and how to get a valid image?
PS: i investigated a lot of answer on the site but they don;t help. If someone will help with my example it will help me a lot.
The error occurs because you bring in an asynchronous context and don't wait for that and directly downloading the image.
You can avoid it by moving the "download part" (see example 1) into the onload function or by packing away the onload and hoping that it will load fast enough (example 2).
Example 1:
//...
img.onload = function() {
console.log(img)
canvas.getContext('2d')?.drawImage(img, 0, 0);
const a = document.createElement('a');
a.download = 'my-image.png';
a.href = canvas.toDataURL();
a.click();
};
Example 2:
//...
canvas.getContext('2d')?.drawImage(img, 0, 0);
const a = document.createElement('a');
a.download = 'my-image.png';
a.href = canvas.toDataURL();
a.click();
We are trying to export SVG picture as PNG using canvg.js but when we click the button "Take a screenshot" the console shows error "vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in v-on handler: "ReferenceError: SVG2PNG is not defined"". Here is the button that calls function "tipka()", I followed this example: "https://jsgao0.wordpress.com/2016/06/02/export-svg-as-png-using-canvg-js-and-canvas/".
<input id='downloadBtn' #click="tipka()" type='button' style="margin-top:500px; position:absolute" value='Download'/>
Here is the script file:
import Canvg from "canvg";
export default {
methods: {
SVG2PNG(svg, callback) {
var canvas = document.createElement("canvas"); // Create a Canvas element.
var ctx = canvas.getContext("2d"); // For Canvas returns 2D graphic.
var data = svg.outerHTML; // Get SVG element as HTML code.
canvg(canvas, data); // Render SVG on Canvas.
callback(canvas); // Execute callback function.
},
generateLink(fileName, data) {
var link = document.createElement("a");
link.download = fileName;
link.href = data;
return link;
},
tipka() {
var element = document.getElementById("svg-01"); // Get SVG element.
SVG2PNG(element, function (canvas) {
// Arguments: SVG element, callback function.
var base64 = canvas.toDataURL("image/png"); // toDataURL return DataURI as Base64 format.
generateLink("SVG2PNG-01.png", base64).click(); // Trigger the Link is made by Link Generator and download.
});
},
},
};
You don't need to put SVG2PNG in the exported methods object (and indeed you shouldn't, in this case, to be able to access it with just SVG2PNG()).
The same goes for generateLink.
Instead, you should be fine with just
import Canvg from "canvg";
function SVG2PNG(svg, callback) {
var canvas = document.createElement("canvas"); // Create a Canvas element.
var ctx = canvas.getContext("2d"); // For Canvas returns 2D graphic.
var data = svg.outerHTML; // Get SVG element as HTML code.
Canvg(canvas, data); // Render SVG on Canvas.
callback(canvas); // Execute callback function.
}
function generateLink(fileName, data) {
var link = document.createElement("a");
link.download = fileName;
link.href = data;
return link;
}
export default {
methods: {
tipka() {
var element = document.getElementById("svg-01"); // Get SVG element.
SVG2PNG(element, function (canvas) {
// Arguments: SVG element, callback function.
var base64 = canvas.toDataURL("image/png"); // toDataURL return DataURI as Base64 format.
generateLink("SVG2PNG-01.png", base64).click(); // Trigger the Link is made by Link Generator and download.
});
},
},
};
I am building a site that allows users to create customized graphics and then download them directly from the page.
I am doing this via the user customizing an svg via a javascript powered form which is then made into a PNG before downloading.
However, now I want to store a copy of the png that the user created so that I can display it on the site's home page. What is the best way to store a copy of the dynamically created graphic? AJAX? HTTP Request? Both are very foreign topics to me so I am unsure how to go about it from here.
For reference here is the code that builds the PNG from the svg and then triggers the download for the user.
var svg = document.getElementById("bg2");
var canvas = document.querySelector("canvas");
function triggerDownload (imgURI) {
var evt = new MouseEvent('click', {
view: window,
bubbles: false,
cancelable: true
});
if(document.getElementById("name").value=="null" || document.getElementById("name").value=="") {
var un = "MakeAGraphic";
} else {
var enteredName = document.getElementById("name").value.replace(/[^a-zA-Z ]/g, "");
var un = `${enteredName}_MakeAGraphic`
}
var a = document.createElement('a');
a.setAttribute('download', `${un}.png`);
a.setAttribute('href', imgURI);
a.setAttribute('target', '_blank');
a.dispatchEvent(evt);
}
btn.addEventListener('click', function () {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = (new XMLSerializer()).serializeToString(svg);
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
var imgURI = canvas
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream');
triggerDownload(imgURI);
};
img.src = url;
});
agree to that answer mentioned by #showdev .
Call
canvas.toBlob(callback, mimeType, qualityArgument);
then call
formData.append("an-image", blob);
then upload formData using the AJAX or fetch API.
see the documents at
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
I currently have a website using D3 and I'd like the user to have the option to save the SVG as an SVG file. I'm using crowbar.js to do this, but it only works on chrome. Nothing happens of safari and IE gives an access denied on the click() method used in crowbar.js to download the file.
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);
How do I download an SVG file based on the SVG element on my website in safari, IE and chrome?
There are 5 steps. I often use this method to output inline svg.
get inline svg element to output.
get svg source by XMLSerializer.
add name spaces of svg and xlink.
construct url data scheme of svg by encodeURIComponent method.
set this url to href attribute of some "a" element, and right click this link to download svg file.
//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.
I know this has already been answered, and that answer works well most of the time. However I found that it failed on Chrome (but not Firefox) if the svg image was large-ish (about 1MB). It works if you go back to using a Blob construct, as described here and here. The only difference is the type argument. In my code I wanted a single button press to download the svg for the user, which I accomplished with:
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);
October 2019 edit:
Comments have indicated that this code will work even without appending downloadLink to document.body and subsequently removing it after click(). I believe that used to work on Firefox, but as of now it no longer does (Firefox requires that you append and then remove downloadLink). The code works on Chrome either way.
Combining Dave's and defghi1977 answers. Here is a reusable function:
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);
}
Invocation example:
saveSvg(svg, 'test.svg')
For this snippet to work you need 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");
};
I tryed every solution here and nothing worked for me. My picture was always smaller than my d3.js canvas.
I had to set the canvas width, height then do a clearRect on the context to make it works. Here is my working version
Export function:
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;
It use FileSaver.js to save the file.
This is my canvas creation, note that i solve the namespace issue here
d3.js canvas creation:
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);
While this question has been answered, I created a small library called SaveSVG which can help save D3.js generated SVG which use external stylesheets or external definition files (using <use> and def) tags.
Based on #vasyl-vaskivskyi 's answer.
<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>
The above code read your chart.css and then embed the css code to your svg file.
I try this and worked for me.
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);
}
In my scenario, I had to use some svg images created using D3.js in other projects. So I opened dev tools and inspected those svg and copied their content. Then created a new blank svg file and pasted the copied content there. Then I used those new svg files in other areas.
And if you want to do it programmatically, then we can use document.getElementById('svgId')
I know this is a basic approach but in case anyone find it useful.
I have the plunkr http://plnkr.co/edit/jdAYlcfO5GDru0MMa2fM?p=preview
$.fn.canvas = function(options){
var hids = $(this).find(':hidden');
source = $(this).get(0);
html2canvas(source, {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
});
};
After click on save, text inside svg get printed twice.
Thats not what desired.
How to fix it?
Use
document.body.replaceChild(document.body,canvas);