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

IT技术 javascript firefox svg html5-canvas base64
2021-01-24 03:37:02

我正在尝试使用画布将外部 svg 图标转换为 base64 png。它适用于除 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;
};

任何人都可以帮助我吗?提前致谢。

3个回答

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

您需要编辑 icon.svg 文件,使其符合上述条件。

关心发布指向该错误的链接吗?跟踪修复进度会很有趣
2021-03-25 03:37:02
此评论显示了一种可能的解决方法:bugzilla.mozilla.org/show_bug.cgi?id=700533#c39
2021-04-01 03:37:02
@tivoni 我已经添加了一个链接,但是在 w3c 定义应该发生什么之前,该错误不会有任何进展。
2021-04-07 03:37:02

如前所述,这是一个开放的错误,由 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 行)中展示了更好的错误处理,我从中导出了这个方法的一部分。

这不是最强大的解决方案,但这个 hack 对我们的目的有效。提取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;
   }
}