被污染的画布无法导出。

315

我想将我的画布保存为一个img。我有这个函数:

function save() {
    document.getElementById("canvasimg").style.border = "2px solid";
    var dataURL = canvas.toDataURL();
    document.getElementById("canvasimg").src = dataURL;
    document.getElementById("canvasimg").style.display = "inline";
}

出现错误:

未捕获的SecurityError:在“HTMLCanvasElement”上执行“toDataURL”失败:污染的画布不能导出。

我该怎么办?


在哪个浏览器中?http://stackoverflow.com/a/21362569/476716声称这是一个错误。 - OrangeDog
1
在Chrome上和在Firefox上 - user3465096
2
https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#Security_and_tainted_canvases - Oliver Joseph Ash
我解决了!只需在localhost上尝试一下。 - mehmet
17个回答

287

出于安全考虑,您的本地驱动器被声明为“其他域”,并会影响画布。

(这是因为您最敏感的信息很可能在本地驱动器上!)

在测试时,请尝试以下解决方法:

  • 将所有与页面相关的文件(.html、.jpg、.js、.css 等)放在桌面上(而不是子文件夹中)。

  • 将您的图像发布到支持跨域共享的网站上(如 dropbox.com 或 GitHub)。确保将您的图像放在 Dropbox 的公共文件夹中,并在下载图像时设置 跨源 标志(var img=new Image(); img.crossOrigin="anonymous" ...)

  • 在开发计算机上安装 Web 服务器(IIS 和 PHP Web 服务器都有免费版本,在本地计算机上可以很好地工作)。


37
谢谢,设置img.crossOrigin属性对我很有帮助! - zumek
6
@markE - 我从localStorage中加载图像数据,而不是从文件或任何URL中加载,然后对其进行了一些操作,例如添加文本。 然后尝试使用toDataURL()将其存储回localStorage。 但是它显示“Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported”。在这种情况下,我没有使用任何外部文件或URL来获取跨域问题。那么为什么会导致此错误? - Sajith
2
@Saijth - 你可能需要验证用于图像的路径。我也遇到了这个问题,因为我直接通过IP(127.0.x.x/)测试访问我的本地虚拟服务器,但是一些图像是通过域名(localhost/)链接的。一旦我使用localhost,它就可以正常工作了。所以确保你没有遇到类似的问题。 - Victor D.
2
(1) 从xampp web服务器查看它,localhost/file,而不是c:/localdisk/file;这样Chrome就不会抱怨安全错误了。 (2) 或者在启动Chrome时使用此标志:--allow-file-access-from-files。 - mosh
1
只需添加另一个可能的问题:如果您尝试导出包含ForeignObject的SVG的画布,则某些浏览器将其标记为污染。 - HairyFotr
显示剩余5条评论

201

在img标签中设置crossorigin属性为Anonymous。

<img crossorigin="anonymous" />

93
对于HTML5画布而言,应该怎么做呢?与图片元素不同。 - graphics123
17
对于 canvas 元素而言,问题的根源通常在你要绘制到其上的某些图像上。因此,在加载图像之前,你只需要找到该图像并设置其 crossOrigin 属性即可解决问题。 - Fernando Echeverria
6
值得注意的是,设置 image.crossOrigin = 'Anonymous' 在图像加载完成后无效。但是如果重新加载是一个选项,你可以尝试使用以下代码:document.querySelectorAll('img').forEach(image => { image.crossOrigin = 'Anonymous'; image.src += ' '; })。 有关更多信息,请参阅 MDN 上的这篇文章 - Rene Hamburger
2
这个对我没用,因为全部小写不起作用,但是 crossOrigin 起作用了。 - Ryan
抱歉,</img> 是什么?已从您的答案中移除。IMG是一个无内容元素,不使用闭合标签。 - Roko C. Buljan
显示剩余2条评论

44

如果你正在使用ctx.drawImage()函数,你可以执行以下操作:

var img = loadImage('../yourimage.png', callback);

function loadImage(src, callback) {
    var img = new Image();

    img.onload = callback;
    img.setAttribute('crossorigin', 'anonymous'); // works for me

    img.src = src;

    return img;
}

在您的回调函数中,现在可以使用 ctx.drawImage 并使用 toDataURL 导出它。


12
这对我没有起作用,仍然收到“不允许导出污染画布”的错误信息。 - Sam Sverko
4
对我来说没问题。谢谢。@SamSverko 确保在设置img.src之前设置属性。 - aijogja
不起作用。显示被CORS策略阻止。 - Aquaphor
该请求已被CORS策略阻止:所请求的资源中没有“Access-Control-Allow-Origin”头。如果不需要访问响应内容,可以将请求模式设置为“no-cors”,以禁用CORS获取资源。 针对“https://img.”的FetchEvent。 - CS QGB
img.setAttribute('crossorigin', 'anonymous'); ✅ 可以正常工作 img.crossorigin = 'anonymous'; ❌ 无法正常工作 - xgqfrms

42
你可能处于以下情况:
1. 尝试使用OpenLayers(版本>=3)在画布中获取地图截图 2. 并查看了导出地图的示例 3. 使用ol.source.XYZ来渲染地图图层
太棒了!
使用ol.source.XYZ.crossOrigin = 'Anonymous'来解决你的困惑。 或者像下面的代码一样:
 var baseLayer = new ol.layer.Tile({
     name: 'basic',
     source: new ol.source.XYZ({
         url: options.baseMap.basic,
         crossOrigin: "Anonymous"
     })
 });

在OpenLayers6中,ES6有一些变化。然而,代码是相似的。
import { XYZ } from 'ol/source'
import { Tile as TileLayer } from 'ol/layer'
const baseLayer = new TileLayer({
    name : 'basic',
    source: new XYZ({
      url: 'example.tile.com/x/y/z', // your tile url
      crossOrigin: 'Anonymous',
      // remove this function config if the tile's src is nothing to decorate. It's usually to debug the src
      tileLoadFunction: function(tile, src) {
        tile.getImage().src = src
      }
    })
  })

此外,不要忘记在响应头中设置access-control-allow-origin: *access-control-allow-origin: [您的白名单来源],如果瓦片是从您自己的服务器请求的。就像这样:

enter image description here

更多详情, 还有这个

2
非常棒。适用于所有资源,例如TileImage等。 - Phil
1
太棒了!正是我自己正在寻找的,对于我正在进行的OpenLayers演示来说是一个简单的修复。 - Marc
我正在使用矢量切片图层,并在将此属性添加到源后,但它仍然无法正常工作! - Mahdi Nazari
我正在使用ol6,但它不起作用,添加了这行代码后没有任何变化。 - Adithya
@JayCummins,我添加了OL6代码。这可能会对您有所帮助。 - sknight

29

在我的情况下,我正在使用类似 canvas.drawImage(video, 0, 0) 的代码从视频中绘制到画布标签。为了解决污染的画布错误,我需要做两件事:

<video id="video_source" crossorigin="anonymous">
    <source src="http://crossdomain.example.com/myfile.mp4">
</video>
  • 确保视频源响应中设置了Access-Control-Allow-Origin头信息(正确设置crossdomain.example.com)
  • 将视频标签设置为crossorigin="anonymous"

14

我使用useCORS: true选项解决了问题

 html2canvas(document.getElementsByClassName("droppable-area")[0], { useCORS:true}).then(function (canvas){
        var imgBase64 = canvas.toDataURL();
        // console.log("imgBase64:", imgBase64);
        var imgURL = "data:image/" + imgBase64;
        var triggerDownload = $("<a>").attr("href", imgURL).attr("download", "layout_"+new Date().getTime()+".jpeg").appendTo("body");
        triggerDownload[0].click();
        triggerDownload.remove();
    });

这是唯一对我有效的解决方案! - lefrost
html2canvas库与OP的问题有何关联?=D - Klesun

12

看起来你正在使用一个没有正确设置Access-Control-Allow-Origin标头的URL的图片,因此出现了问题。你可以从自己的服务器获取该图片并从自己的服务器获取它以避免CORS问题。


1
你能否更精确地回答我的问题,因为我并不是很了解那些概念。我该如何从服务器获取那张图片? - user3465096
你正在从哪里获取那张图片,是从你的服务器还是其他地方? - Prasanna Aarthi
就像这样:loadImage("example.jpg", 0, 0, 500, 300);我可以随意放置图片的URL或者在电脑上同一文件夹中的图片,效果都是一样的。 - user3465096
是的,但我只是在画图上创建了图像,它仍然是一样的。 - user3465096
2
我已经为该文件设置了Access-Control-Allow-Origin:*,但仍然显示错误。 - Harikrishnan

6

请查看来自MDN的启用CORS的图像。 基本上,您必须拥有一个托管图像的服务器,并具有适当的Access-Control-Allow-Origin标头。

<IfModule mod_setenvif.c>
    <IfModule mod_headers.c>
        <FilesMatch "\.(cur|gif|ico|jpe?g|png|svgz?|webp)$">
            SetEnvIf Origin ":" IS_CORS
            Header set Access-Control-Allow-Origin "*" env=IS_CORS
        </FilesMatch>
    </IfModule>
</IfModule>

如果您将这些图像保存到 DOM 存储中,就可以像从您的域名提供的那样访问它们,否则可能会遇到安全问题。

var img = new Image,
    canvas = document.createElement("canvas"),
    ctx = canvas.getContext("2d"),
    src = "http://example.com/image"; // insert image url here

img.crossOrigin = "Anonymous";

img.onload = function() {
    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage( img, 0, 0 );
    localStorage.setItem( "savedImageData", canvas.toDataURL("image/png") );
}
img.src = src;
// make sure the load event fires for cached images too
if ( img.complete || img.complete === undefined ) {
    img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
    img.src = src;
}


虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。- 来自审查 - Gowtham Shiva
谢谢通知。我刚刚添加了来自MDN链接的内容 :) - BerlinaLi

5

和 @markE 的回答类似,您可以通过本地服务器来提供网站服务,这样就不会出现此错误。

如果您的计算机上安装了 PHP(某些旧版 MacOS 已经预装):

  1. 打开终端/cmd
  2. 进入您的网站文件所在的文件夹
  3. 在该文件夹中运行命令 php -S localhost:3000
  4. 打开浏览器,在 URL 地址栏中输入 localhost:3000,您的网站应该正在那里运行。

或者


如果您的计算机上安装了 Node.js:

  1. 打开终端/cmd
  2. 进入您的网站文件所在的文件夹
  3. 在该文件夹中运行命令 npm init -y
  4. 运行 npm install live-server -g 或者在 Mac 上运行 sudo npm install live-server -g
  5. 运行 live-server,它应该会自动在浏览器中打开一个新标签页显示您的网站。

注意:请确保您的文件夹根目录下有 index.html 文件,否则可能会遇到一些问题。


Mac OS没有预装PHP。我有macOS,里面没有关于PHP的任何内容。如果你有PHP,那么是你或其他人安装了它。 - Lukas Liesis
在终端中,您可以运行 php --version 命令来查看您安装的 PHP 版本。 - Ludolfyn
去买一台新的Mac,运行在文章中提到的Apache守护进程脚本,你会发现PHP不是Mac的一部分。除非你运行一些旧版本的操作系统,这从来不是一个好主意,至少在Mac上不是。关于Apache的php模块的那一行甚至不在httpd.conf文件中,但似乎在旧的操作系统中存在。有一行注释:#PHP was deprecated in macOS 11 and removed from macOS 12。你链接的两篇文章都是错误的。php --version显示command not found,因为php不是Mac的一部分 :) - Lukas Liesis
事实上,我有一台一个月之前购买的全新MacBook,它仍然预装并激活了PHP。PHP可以直接使用。我唯一能注意到的是我选择了Intel芯片而不是M1芯片(为了与我正在使用的某些软件兼容)。没有人说它是Mac的一部分。我说它是预装的,它确实是:)尽管当你在终端检查PHP版本时,它会打印一个警告,并且我引用一下:"将来的macOS版本将不包括PHP。"这是一个屏幕截图:https://imgur.com/a/ZZL1SwN - Ludolfyn
事实上,它是否预安装并不重要。事实仍然是,如果您已安装PHP,则可以使用命令“php -S localhost:3000”来提供您的网站,然后您将不会遇到OP所询问的错误。这就是帖子的重点。如果您的计算机上没有安装PHP并且想要使用它而不是Node.js,则只需下载并安装即可。当所有旧版macOS版本中的PHP已经停止流通时,我将更新此帖子以反映这一点-没有问题-但现在我将其保留为“旧版本” :) - Ludolfyn

5

这个可以在 Laravel 中平稳地工作。

首先,您需要将污染的画布转换为Blob。之后,您就可以上传Blob到服务器并将其保存为图像。在ajax调用中返回图像URL。

以下是一个用于上传画布Blob的ajax调用。

$("#downloadCollage").click(function(){
  canvas.toBlob(function(blob){

    var formDataToUpload = new FormData();
    formDataToUpload.append("_token", "{{ csrf_token() }}");
    formDataToUpload.append("image",  blob);

    $.ajax({
        url:"{{ route('selfie_collage_upload') }}",
        data: formDataToUpload,
        type:"POST",
        contentType:false,
        processData:false,
        cache:false,
        dataType:"json",
        error:function(err){
            console.error(err);
        },
        success:function(data){
            window.location.href= data.url;
        },
        complete:function(){
        }
    });
  },'image/png');
  link.click();
});

我遇到了一个错误:“canvas未定义”,我无法解决它。 - user17693898
1
@LaurențiuCozma 在我的代码中,画布是一个变量。例如:canvas = document.getElementById('my-canvas'); - Jatin Mandanka

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