Skip to content
Advertisement

SVG to Image returning blank image blob

I have an interactive drawing app on my website, and I want to create a button where one could share their drawing on FB.

I’m trying to convert the SVG element to a blob, to then pass it to og:image, but I’m having some issues with the conversion.

I have two trials: one doesn’t trigger the onload function for some reason. The other returns an empty blob

both trials work fine on jsfiddle however.

First Attempt

var xmlSerializer = new XMLSerializer();
   
var svgString = xmlSerializer.serializeToString(document.querySelector("#svg"));

var canvas = document.createElement("canvas");

var bounds = {
  width: 1040,
  height: 487
};
canvas.width = bounds.width;
canvas.height = bounds.height;
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {
  type: "image/svg+xml;charset=utf-8"
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
  ctx.drawImage(img, 0, 0);
  var png = canvas.toDataURL("image/png");
  var mg = document.createElement("img");
  mg.setAttribute("src", png);
  document.body.appendChild(mg);
  DOMURL.revokeObjectURL(png);
};
img.id = "testimg";
img.setAttribute("src", url);

Second Attempt

var svgString = new XMLSerializer().serializeToString(document.querySelector("svg"));
  var canvas = document.createElement('CANVAS');
  var ctx = canvas.getContext("2d");
  var DOMURL = self.URL || sel.webkitURL || self;
  var img = new Image();
  var svg = new Blob([svgString], {
    type: "image/svg+xml;charset=utf-8"
  });

  var url = DOMURL.createObjectURL(svg);

  img.onload = function() {
    ctx.drawImage(img, 0, 0);
    var png = canvas.toDataURL("image/png");
    var container = document.createElement('DIV');
    container.innerHTML = '<img src="' + png + '"/>';
    DOMURL.revokeObjectURL(png);
  };
  img.src = url;
  document.body.appendChild(img);

Here’s the app with the two attempts triggered by the two buttons “test1” and “test2”

Answer

The problem lies in the way you did define the xmlns:xlink attributes.
Currently from your page doing document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI will return null. This means that this attribute has been defined in the Document’s namespace (HTML), so when you’ll stringify it using the XMLSerializer, you will actually have two xmlns:xlink attributes on your elements, one in the HTML namespace, and the SVG one that is implied in an SVG embed in an HTML document.
It is invalid to have two same attributes on the same element in SVG, and thus your file is invalid and the image won’t load.

If you are facing this issue it’s certainly because you did set this attribute through JavaScript:

const newUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
newUse.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
newUse.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#foo");
document.querySelector("svg").append(newUse);

console.log("set from markup:", document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
console.log("(badly) set from JS:", document.querySelector("use+use").attributes.getNamedItem("xmlns:xlink").namespaceURI);

// the last <use> has two xmlns:xlink attributes
console.log("serialization:", new XMLSerializer().serializeToString(document.querySelector("svg")));
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
  <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#foo"/>
</svg>

To set it correctly, you need to use setAttributeNS() and use the XMLNS namespace:

const newUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
document.querySelector("svg").append(newUse);
// beware the last "/"
newUse.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");


console.log("set from markup", document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
console.log("(correctly) set from JS", document.querySelector("use+use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
  <use xmlns:xlink="http://www.w3.org/1999/xlink"/>
</svg>

However the best is to not set at all these attributes.
As I said above, SVGs embedded in HTML do automatically have the correct xmlns and xlink namespaces defined without the need for attributes. And since you are creating your elements through JS, you already do define them in the correct namespace too.
So don’t bother with these attributes:

const SVGNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(SVGNS, "svg");
// To be able to draw an SVG image on a canvas in Firefox
// you must set absolute width and height to the root svg node
svg.setAttribute("width", 50);
svg.setAttribute("height", 50);

const target = document.createElementNS(SVGNS, "symbol");
target.id = "target";
const rect = document.createElementNS(SVGNS, "rect");
rect.setAttribute("width", 50);
rect.setAttribute("height", 50);
rect.setAttribute("fill", "green");

const use = document.createElementNS(SVGNS, "use");
// since SVG2 we don't even need to set href in the xlink NS
use.setAttribute("href", "#target");

target.append(rect);
svg.append(target, use);

const svgString = new XMLSerializer().serializeToString(svg);
console.log(svgString); // contains all the NS attributes

const blob = new Blob([svgString], { type: "image/svg+xml" });
const img = new Image();
img.src = URL.createObjectURL(blob);
document.body.append(img);
Advertisement