Firefox错误:使用drawImage将SVG图像渲染到HTML5画布时。

24

我正在尝试使用画布将外部svg图标转换为base64 png格式。在除了Firefox以外的所有浏览器中都可以正常工作,但在Firefox中会抛出错误"NS_ERROR_NOT_AVAILABLE"。

var img = new Image();
img.src = "icon.svg";

img.onload = function() {
    var canvas = document.createElement("canvas");              
    canvas.width = this.width;
    canvas.height = this.height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(this, 0, 0);
    var dataURL = canvas.toDataURL("image/png");
    return dataURL;
};

有人能帮我吗?谢谢您的帮助。


1
你的 SVG 图标有宽度和高度属性吗?如果有,它们是百分比吗? - Robert Longson
嗨,Robert,这是一个SVG文件,而不是SVG DOM元素,我们可以为其分配任何宽度/高度。我正在页面中使用此SVG,如下所示:<image src="icon.svg" width="32" height="32">。 - Aneesh
那并没有回答我的问题。icon.svg文件的根<svg>元素上是否有宽度/高度属性?如果有,这些属性值是百分比吗? - Robert Longson
SVG图标是使用Adobe Illustrator生成的,我在SVG文件中没有看到任何宽度/高度。 <svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" x="0px" y="0px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> <metadata><?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> - Aneesh
4
我有同样的问题,只是调用drawImage()时甚至没有抛出错误。在<svg>中添加width/height确实解决了它! - Eldritch Conundrum
3个回答

61

除非根<svg>元素具有宽度/高度属性且这些属性不是百分比,否则Firefox不支持将SVG图像绘制到画布上。 这是一个长期存在的问题

您需要编辑icon.svg文件以满足上述条件。


方便贴一下那个错误的链接吗?跟踪修复进度会很有趣。 - tivoni
@tivoni 我已经添加了一个链接,但在w3c定义应该发生什么之前,这个错误将没有任何进展。 - Robert Longson
这个注释展示了一个可能的解决方法:https://bugzilla.mozilla.org/show_bug.cgi?id=700533#c39 - Miguel Sánchez Villafán

6

如上所述,这是一个开放的错误,由于Firefox在绘制到画布时接受的SVG尺寸规范的限制导致。有个解决方法。

Firefox要求SVG本身具有明确的宽度和高度属性。我们可以通过获取SVG作为XML并修改它来添加这些属性。

var img = new Image();
var src = "icon.svg";

// request the XML of your svg file
var request = new XMLHttpRequest();
request.open('GET', src, true)

request.onload = function() {
    // once the request returns, parse the response and get the SVG
    var parser = new DOMParser();
    var result = parser.parseFromString(request.responseText, 'text/xml');
    var inlineSVG = result.getElementsByTagName("svg")[0];
    
    // add the attributes Firefox needs. These should be absolute values, not relative
    inlineSVG.setAttribute('width', '48px');
    inlineSVG.setAttribute('height', '48px');
    
    // convert the SVG to a data uri
    var svg64 = btoa(new XMLSerializer().serializeToString(inlineSVG));
    var image64 = 'data:image/svg+xml;base64,' + svg64;
    
    // set that as your image source
    img.src = img64;

    // do your canvas work
    img.onload = function() {
        var canvas = document.createElement("canvas");              
        canvas.width = this.width;
        canvas.height = this.height;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(this, 0, 0);
        var dataURL = canvas.toDataURL("image/png");
        return dataURL;
    };
}
// send the request
request.send();

这是该解决方案最基本的版本,没有处理在检索XML时出现错误的功能。更好的错误处理方法在此inline-svg处理器中演示(约第110行),我从中提取了部分方法。


这是一个非常好的答案,对于大多数SVG实现都有效。为了提高其兼容性,我用以下代码替换了btoa(new XMLSerializer().serializeToString(inlineSVG));这一行:Buffer.Buffer.from(new XMLSerializer().serializeToString(inlineSVG), 'utf').toString('base64');(使用独立的NodeJS Buffer插件,可以在此处下载:https://bundle.run/buffer@6.0.3) - Milun
http://phrogz.net/SVG/svg_to_png.xhtml 在Firefox、Chrome和Safari中有所不同,因此您真的需要控制SVG文件,或者使用本答案中的方法。 - forresto

0

这不是最健壮的解决方案,但这个技巧对我们的目的起到了作用。提取viewBox数据并使用这些尺寸作为宽度/高度属性。

只有当第一个遇到的viewBox具有准确表示SVG文档大小的大小时,才能正常工作,这在所有情况下都不是真实的。

   // @svgDoc is some SVG document.
   let svgSize = getSvgViewBox(svgDoc);

   // No SVG size?
   if (!svgSize.width || !svgSize.height) {
      console.log('Image is missing width or height');

   // Have size, resolve with new SVG image data.
   } else {
      // Rewrite SVG doc
      let unit = 'px';
      $('svg', svgDoc).attr('width', svgSize.width + unit);
      $('svg', svgDoc).attr('height', svgSize.height + unit);

      // Get data URL for new SVG.
      let svgDataUrl = svgDocToDataURL(svgDoc);
   }


function getSvgViewBox(svgDoc) {
   if (svgDoc) {
      // Get viewBox from SVG doc.
      let viewBox = $(svgDoc).find('svg').prop('viewBox').baseVal;

      // Have viewBox?
      if (viewBox) {
         return {
            width: viewBox.width,
            height: viewBox.height
         }
      }
   }

   // If here, no viewBox found so return null case.
   return {
      width: null,
      height: null
   }
}

function svgDocToDataURL(svgDoc, base64) {
   // Set SVG prefix.
   const svgPrefix = "data:image/svg+xml;";

   // Serialize SVG doc.
   var svgData = new XMLSerializer().serializeToString(svgDoc);

   // Base64? Return Base64-encoding for data URL.
   if (base64) {
      var base64Data = btoa(svgData);
      return svgPrefix + "base64," + base64Data;

   // Nope, not Base64. Return URL-encoding for data URL.
   } else {
      var urlData = encodeURIComponent(svgData);
      return svgPrefix + "charset=utf8," + urlData;
   }
}

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