将内联SVG转换为Base64字符串

61

我想将一个内联SVG图像发送到一个PHP脚本,使用Imagick将其转换为PNG。为此,我必须知道如何从内联SVG中获取一个base64字符串。对于canvas对象来说,它是一个简单的“.toDataURL()”,但这在内联SVG中不起作用,因为它不是元素的全局函数。

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

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

但如何对内联SVG进行操作呢?


这可能会有所帮助:https://dev59.com/IW035IYBdhLWcg3wT-RC - GoBusto
有人找到解决方案了吗? - Anjana
6个回答

120

使用XMLSerializer将DOM转换为字符串

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

然后btoa可以将其转换为base64格式。

var encodedData = window.btoa(s);

只需在数据URL前添加data:image/svg+xml;base64,,然后就可以了。


3
由于文本中含有非Latin1字符(btoa无法处理),因此此代码在示例svg上无法正常工作。已在Chrome 40上进行测试。 - collapsar
8
我需要对SVG进行反转义和编码,方法是这样的:img.setAttribute("src", "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(s)))) - TimDog
@AdamYoungers,您没有任何id="svg"的元素,通常最好单独询问此类问题。 - Robert Longson
哎呀!忘记将其作为HTML选择器。 - Adam Youngers
2
window.btoa(s)在我的应用程序中无法工作,替换为以下内容: base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) { return '&#' + c.charCodeAt(0) + ';'; })); - Prem Sanil
显示剩余7条评论

24

我只是尝试收集和解释有关这个问题的所有伟大想法。 这适用于Chrome 76和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);

1
在MDN中有一个标记,不再使用unescape了。https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/unescape - pery mimon
1
@perymimon 这让我有点困惑。MDN建议使用decodeURIComponent而不是unescape。那么decodeURIComponent(encodeURIComponent())有什么意义呢? - Teepeemm
@teepeemm 如果你在那之后仍然执行 'btoa',那么可能不行。 - pery mimon
2
谢谢,unescape 帮我找到了 decodeURI,在我的情况下起到了同样的作用。 - atom
这个功能非常棒,但它忽略了所有的CSS样式。有没有一种方法在渲染图像之前注入样式? - nixkuroi

10
您可以按照以下相对简单的方式进行操作。简洁版本如下:
  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/


本答案的主要优点是它没有使用不必要的XMLSerializer,因为简单的本地element.outerHTML也能起到同样的作用 :) - jave.web

3

在使用基于SVG的楼层平面图编辑器时,我遇到了同样的问题。在绘制楼层平面图后,我们需要将其保存以供以后使用。长话短说,代码胜过言辞,这是最终为我工作的代码:

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);  
}

-1

这行代码将执行转换:

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

请确保已选择正确的字符集/编码!


如果您不对其进行base64编码,则需要对其进行URL编码,因为#在颜色中经常出现且是保留字符。 - Robert Longson
@RobertLongson 您说得对,某种编码是必要的(b64很方便)。我指的是引用的SO答案中Base64对象中的特定额外代码。任何将其输入严格视为八位字节序列(而不是字符流)处理的base64编码器都可以。 - collapsar

-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")

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