canvas.ToBlob 在 Chrome 工作,但不使用 firefox

canvas.ToBlob working in Chrome, but not working with firefox

Chrome:v96 火狐浏览器:v95

我正在尝试从浏览器将 SVG 图像下载为 PNG 图像。这似乎适用于 Chrome,但我正在使用 Firefox 下载空白图像。知道为什么吗?

export function downloadSvgImage(svgElement: HTMLElement, name: string) {

    const xml = new XMLSerializer().serializeToString(svgElement);
    const svg64 = window.btoa(xml);
    const b64Start = 'data:image/svg+xml;base64,';

    const viewBox = svgElement.getAttribute('viewBox');
    const dimensionArr = viewBox.split(' ');
    const width = parseInt(dimensionArr[2]);
    const height = parseInt(dimensionArr[3]);

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = height;

    const image = new Image();

    image.onload = () => {

        canvas.getContext('2d').drawImage(image, 0, 0, width, height);

            canvas.toBlob((blob: any) => {
                const anchor = document.createElement('a');
                anchor.download = `${name}.png`;
                anchor.href = URL.createObjectURL(blob);
                anchor.click();
                URL.revokeObjectURL(blob);
            }, 'image/png');

    };
    image.src = b64Start + svg64;
}

我将捕获尺寸的代码更新为以下内容,它按预期通过 Firefox 下载 SVG 图像:

...
  let dimensionX = svgElement.viewBox.baseVal.width;
  let dimensionY = svgElement.viewBox.baseVal.height;
  if (dimensionX == 0 || dimensionY == 0) {
    dimensionX = svgElement.getBBox().width;
    dimensionY = svgElement.getBBox().height;
  }
  const width = dimensionX;
  const height = dimensionY;
...

function downloadSvgImage(svgElement, name) {
  const xml = new XMLSerializer().serializeToString(svgElement);
  const svg64 = window.btoa(xml);
  const b64Start = "data:image/svg+xml;base64,";

  let dimensionX = svgElement.viewBox.baseVal.width;
  let dimensionY = svgElement.viewBox.baseVal.height;
  if (dimensionX == 0 || dimensionY == 0) {
    dimensionX = svgElement.getBBox().width;
    dimensionY = svgElement.getBBox().height;
  }

  const width = svgElement.clientWidth * 0.5; // dimensionX;
  const height = svgElement.clientHeight * 0.5; // dimensionY;

  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;

  const image = new Image();

  image.onload = () => {
    canvas.getContext("2d").drawImage(image, 0, 0, width, height);
    const url = canvas.toDataURL("image/png", 1);
    const anchor = document.createElement("a");
    anchor.download = `${name}.png`;
    anchor.href = url;
    anchor.click();

    setTimeout(() => URL.revokeObjectURL(url), 0);
  };
  image.src = b64Start + svg64;
}

window.onload = function(){
  const svg = document.querySelector("#cartman");
  svg.setAttribute("width", svg.clientWidth);
  svg.setAttribute("height", svg.clientHeight);
  console.log("Download starting in 3 seconds...");
  setTimeout(() => downloadSvgImage(svg, "cartman-sp"), 3000);
}
<svg xmlns="http://www.w3.org/2000/svg" id="cartman" viewBox="0 0 104 97">
  <path d="M14,85l3,9h72c0,0,5-9,4-10c-2-2-79,0-79,1" fill="#7C4E32"/>
  <path d="M19,47c0,0-9,7-13,14c-5,6,3,7,3,7l1,14c0,0,10,8,23,8c14,0,26,1,28,0c2-1,9-2,9-4c1-1,27,1,27-9c0-10,7-20-11-29c-17-9-67-1-67-1" fill="#E30000"/>
  <path d="M17,32c-3,48,80,43,71-3 l-35-15" fill="#FFE1C4"/>
  <path d="M17,32c9-36,61-32,71-3c-20-9-40-9-71,3" fill="#8ED8F8"/>
  <path d="M54,35a10 8 60 1 1 0,0.1zM37,38a10 8 -60 1 1 0,0.1z" fill="#FFF"/>
  <path d="M41,6c1-1,4-3,8-3c3-0,9-1,14,3l-1,2h-2h-2c0,0-3,1-5,0c-2-1-1-1-1-1l-3,1l-2-1h-1c0,0-1,2-3,2c0,0-2-1-2-3M17,34l0-2c0,0,35-20,71-3v2c0,0-35-17-71,3M5,62c3-2,5-2,8,0c3,2,13,6,8,11c-2,2-6,0-8,0c-1,1-4,2-6,1c-4-3-6-8-2-12M99,59c0,0-9-2-11,4l-3,5c0,1-2,3,3,3c5,0,5,2,7,2c3,0,7-1,7-4c0-4-1-11-3-10" fill="#FFF200"/>
  <path d="M56,78v1M55,69v1M55,87v1" stroke="#000" stroke-linecap="round"/>
  <path d="M60,36a1 1 0 1 1 0-0.1M49,36a1 1 0 1 1 0-0.1M57,55a2 3 0 1 1 0-0.1M12,94c0,0,20-4,42,0c0,0,27-4,39,0z"/>
  <path d="M50,59c0,0,4,3,10,0M56,66l2,12l-2,12M25,50c0,0,10,12,23,12c13,0,24,0,35-15" fill="none" stroke="#000" stroke-width="0.5"/>
</svg>

希望能解决您的问题。

注意:您必须在本地环境中测试此代码,因为从框架下载会被浏览器有意阻止。


更新

所以我意识到我最初的“修复”实际上并没有起作用,因为它仍在下载一个小的空 canvas。我最终发现 Firefox 在 canvas 元素内渲染 SVG 有一个 long-standing bug 除非在 <SVG> 根元素上指定了 widthheight 属性具有非基于百分比的值。所以我通过手动将它们设置为 <SVG> 的客户维度来解决这个问题:

svg.setAttribute("width", svg.clientWidth);
svg.setAttribute("height", svg.clientHeight);

您可以将 image/png 的维度定义为 <SVG>width & height 的百分比。

  const width = svgElement.clientWidth * 0.5; // half the width of the original svg
  const height = svgElement.clientHeight * 0.5; // half the height of the original svg
  ...
  canvas.width = width;
  canvas.height = height;

我还更新了数据 URL 生成的代码,以使用 canvas 元素的 .toDataURL 方法,并将 revokeObjectURL 延迟到下载后的片刻初始化:

    canvas.getContext("2d").drawImage(image, 0, 0, width, height);
    const url = canvas.toDataURL("image/png", 1);
    const anchor = document.createElement("a");
    anchor.download = `${name}.png`;
    anchor.href = url;
    anchor.click();
    setTimeout(() => URL.revokeObjectURL(url), 0);

我希望这次更新能正式解决这个问题。