从包含foreignObject的SVG中获取canvas dataURI存在问题

8

你好,我有以下问题。

我正在生成一个SVG图像(https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_DOM_objects_into_a_canvas)。

图像已经正确生成并且看起来不错。现在我需要获取数据URI,但每次我尝试从canvas.toDataURL()获取时都会出现以下信息:未捕获的DOM异常:无法在HTMLCanvasElement上执行'toDataURL':污染的画布可能无法导出。(...)

我创建了一些示例代码来说明这种情况。

</!DOCTYPE html>
<html>
<head>
    <title>SVG to PNG</title>
</head>
<body>
    <canvas id="canvas" style="border:2px solid black;" width="200" height="200">
</canvas>


<script type="text/javascript">

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
               '<foreignObject width="100%" height="100%">' +
               '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
                 '<em>I</em> like ' + 
                 '<span style="color:white; text-shadow:0 0 2px blue;">' +
                 'beer</span>' +
               '</div>' +
               '</foreignObject>' +
            '</svg>';

var domURL = window.URL || window.webkitURL || window;

var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml'});
var url = domURL.createObjectURL(svg);

img.onload = function () {
  ctx.drawImage(img, 0, 0);
  domURL.revokeObjectURL(url);
  var dataURL = canvas.toDataURL();
  console.log(dataURL);
};

img.src = url;

</script>
</body>
</html>

这段代码生成以下图像:

enter image description here

这是正确的,但是当执行以下代码时:

var dataURL = canvas.toDataURL(); console.log(dataURL);

它会抛出错误。我在图片onload方法中执行此操作,以允许图像完成绘制到画布上。如果我尝试从onload方法外获取dataURL,则画布尚未加载完成,因此会给我一个空图像。 我一直在搜索,但还没有找到答案。这与CORS有关。我安装了chrome插件CORS并添加了img,但问题在于这只是一个示例,基于CORS的解决方案将无用,因为我正在使用一个运行在受限制chromium浏览器上的应用程序(我的意思是浏览器只显示web应用程序,您除了与应用程序交互之外什么都不能做)。

请注意,您可以通过以下方式获取数据URI:

检查 --> 网络 --> 选择图像并在源面板中打开,然后就可以复制图像作为数据URI了。 我以这种方式获取base64图像,如果使用此类解码器(http://base64online.org/decode/),则会正确显示图像。 因此,问题肯定是在canvas绘制之前获取数据URI。

有任何想法吗?

谢谢!

敬礼

Mauro


什么是“污染”的画布?虽然您可以在画布中使用未经CORS批准的图像,但这样会污染画布。一旦画布被污染,您就不能再从画布中获取数据了。例如,您不能再使用canvas toBlob(),toDataURL()或getImageData()方法;这样做会抛出安全错误。此举可防止用户在未经许可的情况下使用图像从远程网站提取信息而导致私人数据暴露。 (来源:https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image) - Tim Vermaelen
@TimVermaelen,这里没有涉及跨域问题... - Kaiido
只是想知道什么是“污染画布”。因此,斜体描述接近于您尝试的原因。 - Tim Vermaelen
1个回答

11
这与CORS无关。您没有向外部资源发出任何请求。
问题在于,绘制SVG(尤其是包含HTML元素的SVG)对浏览器来说是一项非常敏感的操作。
这就是为什么这种技术有很多限制(无法加载外部资源,脚本被忽略,无法从外部CSS样式化内容,所有默认浏览器和操作系统样式都被禁用以避免指纹识别等)。
我不为Safari或Chrome工作,所以我不能确定,但我认为它们不能在其中一个点上提供足够的安全性(Safari在v9中意识到了这一点,这是chrome中已知的问题)。
因此,他们会污染画布,锁定其任何导出方法。(请注意,IE < Edge也会污染画布,只要任何SVG已经被绘制到画布上,出于类似的安全原因)
如果你想栅格化DOM元素,那么你需要解析DOM及其应用样式,并使用画布绘图方法重现它。
这样做,您可以绕过大多数安全问题,而且没有UA会污染画布。一些库就是这样做的,其中最著名的是html2canvas
如果你想绘制这张图片,那么你可以不使用foreignObject重新编写它(svg文本比canvas具有更多选项),然后使用像canvg这样的库在你的画布上呈现它(否则IE会像之前说的那样污染画布)
注意:Chrome的问题可以通过解决方法解决,但我不确定这是否是一个好主意,它仍然无法在Safari和IE < Edge中工作,并且Chrome也可能忘记阻止它,将在下一个版本中加以处理。无论如何,以下是方法:

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
  '<foreignObject width="100%" height="100%">' +
  '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
  '<em>I</em> like ' +
  '<span style="color:white; text-shadow:0 0 2px blue;">' +
  'beer</span>' +
  '</div>' +
  '</foreignObject>' +
  '</svg>';


var img = new Image();
// instead of a blobURL, if we use a dataURL, chrome seems happy...
var url = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(data);

img.onload = function() {
  c.width = this.width;
  c.height = this.height;
  ctx.drawImage(img, 0, 0);
  var dataURL = canvas.toDataURL();
  console.log(dataURL);
};

img.src = url;
<canvas id="c"></canvas>


1
你的解决方案在Chrome、Firefox和Safari中都有效 :) - bumbeishvili
你的解决方案救了我的命,谢谢! - Anoesj Sadraee
@Kaiido 但是如果我在foreignObject中添加img标签,为什么它不起作用呢? - Oybek Odilov
1
@OybekOdilov,你无法从通过<img>加载的文档中发出任何请求,你必须将图像数据作为data:URL包含在其中。 - Kaiido
我们将在Safari和Firefox中面临数据URL限制的问题。 - Samyak Jain

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