60

I want to send an inline SVG image to a PHP script to Convert it to PNG with Imagick. For that I have to know how to get a base64 String out on an inline SVG. For canvas objects its a simple ".toDataURL()" but that doesn't work with inline SVGs, because its not a global function of elements.

test = function(){
    var b64 = document.getElementById("svg").toDataURL();
    alert(b64);
}

http://jsfiddle.net/nikolang/ccx195qj/1/

But how to do it for inline SVGs?

Niko Lang
  • 847
  • 2
  • 9
  • 17

6 Answers6

118

Use XMLSerializer to convert the DOM to a string

var s = new XMLSerializer().serializeToString(document.getElementById("svg"))

and then btoa can convert that to base64

var encodedData = window.btoa(s);

Just prepend the data URL intro i.e. data:image/svg+xml;base64, and there you have it.

Robert Longson
  • 118,664
  • 26
  • 252
  • 242
  • 3
    This doesn't work on the sample svg due to non-Latin1 characters in the text (btoa barfs on that). Tested on chrome 40. – collapsar Feb 11 '15 at 10:04
  • It does indeed, on chrome too (stupid me had selected the wrong charset). – collapsar Feb 11 '15 at 11:08
  • The solution works perfectly without converting it to base64. So only Serialising and then outputting it as a SVG object in HTML will work. – richardj Jun 24 '16 at 18:03
  • @richardj That would work sometimes i.e. on some content. You'd have to url encode the string to be sure of it working on any content. – Robert Longson Jun 24 '16 at 20:35
  • 8
    I needed to unescape and encode the svg like so: `img.setAttribute("src", "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(s))))` – TimDog Jan 17 '18 at 05:05
  • I get Uncaught TypeError: Failed to execute 'serializeToString' on 'XMLSerializer': parameter 1 is not of type 'Node'. What am I doing wrong? https://codepen.io/cssguru/pen/XZmGPO – Adam Youngers Feb 02 '18 at 02:09
  • @AdamYoungers You don't have any elements with id="svg", it's generally best to ask such things separately though – Robert Longson Feb 02 '18 at 02:14
  • Oh snap! Overlooked that as an HTML selector. – Adam Youngers Feb 02 '18 at 02:53
  • this is correct only if you svg+xml base64 works for you. this will not help if you need base64 png or jpeg – mcmxc Feb 15 '19 at 15:36
  • 2
    window.btoa(s) didnt work in my application, replacing this base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) { return '' + c.charCodeAt(0) + ';'; })); – Prem Sanil Jun 07 '19 at 07:07
  • This worked for me, but instead of using XMLSerializer, I used ReactDOMServer.renderToString as my element was not in the DOM but defined as a constant in React. Works just fine with btoa and url() in CSS. – Xevion Nov 24 '22 at 20:09
  • @Prem Sanil: Great. Also I expanded the interval.. base64 = btoa(str.replace(/[\u0080-\uffff]/g, c => \`${c.charCodeAt(0)};\`)); – Bachor Mar 13 '23 at 16:18
23

I just try to collect and explain all great ideas on this issue. This works on both Chrome 76 and Firefox 68

var svgElement = document.getElementById('svgId');

// Create your own image
var img = document.createElement('img');

// Serialize the svg to string
var svgString = new XMLSerializer().serializeToString(svgElement);

// Remove any characters outside the Latin1 range
var decoded = unescape(encodeURIComponent(svgString));

// Now we can use btoa to convert the svg to base64
var base64 = btoa(decoded);

var imgSource = `data:image/svg+xml;base64,${base64}`;

img.setAttribute('src', imgSource);
Anh Hoang
  • 2,242
  • 3
  • 22
  • 23
  • 1
    in MDN there is a mark to not use `unescape` any more https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/unescape – pery mimon Mar 08 '20 at 10:10
  • 1
    @perymimon That leaves me a bit confused. MDN says to use `decodeURIComponent` instead of `unescape`. Is there a point to `decodeURIComponent(encodeURIComponent())`? – Teepeemm Jul 09 '20 at 21:26
  • @teepeemm probably not if after that you do 'btoa' any way – pery mimon Jul 11 '20 at 05:09
  • 2
    Thank you, unescape helped me find decodeURI, which works the same in my case. – atom Jun 17 '21 at 08:07
  • This works amazingly, except it ignores all css styles. Is there a way to inject styles before renders the image? – nixkuroi Dec 04 '22 at 03:14
9

You can do it relatively straightforwardly as follows. The short version is

  1. Make an image not attached to the dom
  2. Set its size to match the source svg
  3. base64 encode the source svg, append the relevant data, set img src
  4. Get the canvas context; .drawImage the image

    <script type="text/javascript">
    
    
        window.onload = function() {
            paintSvgToCanvas(document.getElementById('source'), document.getElementById('tgt'));
        }
    
    
    
        function paintSvgToCanvas(uSvg, uCanvas) {
    
            var pbx = document.createElement('img');
    
            pbx.style.width  = uSvg.style.width;
            pbx.style.height = uSvg.style.height;
    
            pbx.src = 'data:image/svg+xml;base64,' + window.btoa(uSvg.outerHTML);
            uCanvas.getContext('2d').drawImage(pbx, 0, 0);
    
        }
    
    
    </script>
    

    <svg xmlns="http://www.w3.org/2000/svg" width="467" height="462" id="source">
      <rect x="80" y="60" width="250" height="250" rx="20" style="fill:#ff0000; stroke:#000000;stroke-width:2px;" />
      <rect x="140" y="120" width="250" height="250" rx="40" style="fill:#0000ff; stroke:#000000; stroke-width:2px; fill-opacity:0.7;" />
    </svg>
    
    <canvas height="462px" width="467px" id="tgt"></canvas>
    

JSFiddle: https://jsfiddle.net/oz3tjnk7/

jave.web
  • 13,880
  • 12
  • 91
  • 125
John Haugeland
  • 9,230
  • 3
  • 37
  • 40
  • Main **+** of this answer, is that it does **not use unnecessary `XMLSerializer`**, because simple **native `element.outerHTML` works** too :) – jave.web Oct 07 '19 at 17:41
2

I had the same problem while working on SVG based Floorplan Editor, after drawing a floorplan we have to save it for later use. Long story, code is better than talking, Here is the final code which worked for me:

async saveFloorplan() {
  const svgElem = document.querySelector('#svg-element');
  let xmlSource = new XMLSerializer().serializeToString(svgElem);

  if (!xmlSource.match(/^<svg[^>]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)) {
    xmlSource = xmlSource.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
  }
  if (!xmlSource.match(/^<svg[^>]+"http:\/\/www\.w3\.org\/1999\/xlink"/)) {
    xmlSource = xmlSource.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
  }
  // add xml declaration
  xmlSource = `<?xml version="1.0" standalone="no"?>\r\n${xmlSource}`;

  const svg64 = encodeURIComponent(xmlSource);
  const b64Start = 'data:image/svg+xml;charset=utf-8,';

  const imgEl = new Image();
  const image64 = b64Start + svg64;
  imgEl.src = image64;

  const blobResp = await fetch(imgEl.src);
  const blob = await blobResp.blob();

  const payload = {...}

  payload.fileExtension = 'svg';
  payload.fileSize = blob.size;

  const formData = new FormData();
  formData.append('file', blob);

  const uploaded = await api.uploadFile(formData);  
}
Humoyun Ahmad
  • 2,875
  • 4
  • 28
  • 46
-1

This line will perform the conversion:

window.btoa($("svg").wrap("<div id='xyz'/>").parent().html());

Make sure that the proper charset/encoding has been selected!

collapsar
  • 17,010
  • 4
  • 35
  • 61
  • If you don't base64 encode it you'll need to URL encode it as # is a reserved character which occurs rather frequently in colours. – Robert Longson Feb 11 '15 at 10:02
  • @RobertLongson You are right that some sort of encoding is necessary (b64 being convenient). I was referring to the specific extra code in the `Base64` object from the referenced SO answer. Any base 64 encoder that treats its input strictly as an octet sequence (as opposed to a character stream) will do. – collapsar Feb 11 '15 at 10:07
-1
// 1. get the element
let e = document.getElementById("svg");

// 2. get the string representation of the element
var s = new XMLSerializer().serializeToString(e);
// or 
var s = e.outerHTML;

// 3. convert the string to url
// convert to utf8
var url = "data:image/svg+xml;utf8," + encodeURIComponent(s);
// convert to base64
var url = "data:image/svg+xml;base64," + window.btoa(unescape(encodeURIComponent(s)));
// or
var url = "data:image/svg+xml;base64," + Buffer.from(s, "utf-8").toString("base64")