使用canvas.toDataURL时如何设置crossOrigin属性?

时间:2016-01-06 18:02:35

标签: javascript html5 canvas openlayers-3 todataurl

所以我正在尝试为我正在构建的OpenLayers 3应用程序创建一个打印映射函数。我知道他们的example,但每当我尝试使用它时,我都会遇到可怕的污点画布问题。我已经阅读了整个互联网并且遇到了人们首先正确设置CORS(完成和完成)但也要做:

          var img = new Image();
          img.setAttribute('crossOrigin', 'anonymous');
          img.src = url;

上面描述了here

我的问题是,我以前从未真正使用过toDataURL(),而且我不确定如何确保正在创建的图像在被激活之前正确设置了crossOrigin属性:

Error: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

有什么想法吗?

我见过this。我的问题是他们如何将其纳入一个有效的功能。类似的东西:

    var printMap = function(){
     var img = new Image();
     img.setAttribute('crossOrigin', 'anonymous');
     img.src = url;
     img.onload = function() {
      var canvas = document.getElementsByTagName('canvas');
      var dataURL = canvas.toDataURL("image/png");
      console.log(dataURL);
     };
   };

2 个答案:

答案 0 :(得分:7)

如果浏览器支持crossOrigin属性/属性(现在位于FF,Chrome,最新的Safari和Edge),但服务器无法回答使用正确的标题Access-Control-Allow-Origin: *),然后触发img的onerror事件。

因此,如果我们想要绘制图像,我们可以处理此事件并删除属性 对于不处理此属性的浏览器,测试画布是否被污染的唯一方法是将toDataURL调用到try catch块中。

以下是一个例子:

var urls = 
    ["http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png", 
     "http://lorempixel.com/200/200"];

	var tainted = false;

	var img = new Image();
	img.crossOrigin = 'anonymous';

	var canvas = document.createElement('canvas');
	var ctx = canvas.getContext('2d');
	document.body.appendChild(canvas);

	var load_handler = function() {
	  canvas.width = 200;
	  canvas.height = 200;

	  ctx.fillStyle = 'white';
	  ctx.font = '15px sans-serif';

	  ctx.drawImage(this, 0, 0, 200, 200*(this.height/this.width));

	  // for browsers supporting the crossOrigin attribute
	  if (tainted) {
	    ctx.strokeText('canvas tainted', 20, 100);
	    ctx.fillText('canvas tainted', 20, 100);
	  } else {
	    // for others
	    try {
	      canvas.toDataURL();
	    } catch (e) {
	      tainted = true;
	      ctx.strokeText('canvas tainted after try catch', 20, 100);
	      ctx.fillText('canvas tainted after try catch', 20, 100);
	    }
	  }
	};

	var error_handler = function() {
	  // remove this onerror listener to avoid an infinite loop
	  this.onerror = function() {
	    return false
	  };
	  // certainly that the canvas was tainted
	  tainted = true;

	  // we need to removeAttribute() since chrome doesn't like the property=undefined way...
	  this.removeAttribute('crossorigin');
	  this.src = this.src;
	};

	img.onload = load_handler;
	img.onerror = error_handler;

	img.src = urls[0];

	btn.onclick = function() {
	  // reset the flag
	  tainted = false;

	  // we need to create a new canvas, or it will keep its marked as tainted flag
	  // try to comment the 3 next lines and switch multiple times the src to see what I mean
	  ctx = canvas.cloneNode(true).getContext('2d');
	  canvas.parentNode.replaceChild(ctx.canvas, canvas);
	  canvas = ctx.canvas;

	  // reset the attributes and error handler
	  img.crossOrigin = 'anonymous';
	  img.onerror = error_handler;
	  img.src = urls[+!urls.indexOf(img.src)];
	};
<button id="btn"> change image src </button><br>

但是,因为toDataURL可能只是一个非常重要的检查,并且try catch中的代码被去优化,对于旧版浏览器来说,更好的替代方法是创建1px * 1px测试器画布,在其上绘制图像首先在try-catch块中调用它的toDataURL:

var urls = ["http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png", "http://lorempixel.com/200/200"];

	var img = new Image();
	img.crossOrigin = 'anonymous';

	var canvas = document.createElement('canvas');
	var ctx = canvas.getContext('2d');
	document.body.appendChild(canvas);

	 //create a canvas only for testing if our images will taint our canvas or not;
	var taintTester = document.createElement('canvas').getContext('2d');
	taintTester.width = 1;
	taintTester.height = 1;

	var load_handler = function() {
	  // our image flag
	  var willTaint = false;
	  // first draw on the tester
	  taintTester.drawImage(this, 0, 0);
	  // since it's only one pixel wide, toDataURL is way faster
	  try {
	    taintTester.canvas.toDataURL();
	  } catch (e) {
	    // update our flag
	    willTaint = true;
	  }
	  // it will taint the canvas
	  if (willTaint) {
	    // reset our tester
	    taintTester = taintTester.canvas.cloneNode(1).getContext('2d');


	    // do something
	    ctx.fillStyle = 'rgba(0,0,0,.7)';
	    ctx.fillRect(0, 75, ctx.measureText('we won\'t diplay ' + this.src).width + 40, 60);
	    ctx.fillStyle = 'white';
	    ctx.font = '15px sans-serif';
	    ctx.fillText('we won\'t diplay ' + this.src, 20, 100);
	    ctx.fillText('canvas would have been tainted', 20, 120);
        
	  } else {
        
	    // all clear
	    canvas.width = this.width;
	    canvas.height = this.height;

	    ctx.fillStyle = 'white';
	    ctx.font = '15px sans-serif';

	    ctx.drawImage(this, 0, 0);
	  }
	};

	var error_handler = function() {
	  // remove this onerror listener to avoid an infinite loop
	  this.onerror = function() {
	    return false
	  };

	  // we need to removeAttribute() since chrome doesn't like the property=undefined way...
	  this.removeAttribute('crossorigin');
	  this.src = this.src;
	};

	img.onload = load_handler;
	img.onerror = error_handler;

	img.src = urls[0];

	btn.onclick = function() {
	  // reset the attributes and error handler
	  img.crossOrigin = 'anonymous';
	  img.onerror = error_handler;
	  img.src = urls[+!urls.indexOf(img.src)];
	};
<button id="btn">change image src</button>

注意

交叉原始请求不是污染画布的唯一方法:
在IE&lt;边缘,在画布上绘制一个svg会污染画布以解决安全问题,同样,如果在画布上绘制的svg中存在<foreignObject>,最新的Safari确实会污染画布,最后,任何UA都会污染画布画布是否涂有其他受污染的画布。

因此,在这些情况下检查画布是否被污染的唯一解决方案是try-catch,最好是在1px xpp 1测试画布上进行。

答案 1 :(得分:4)

所以Pointy和Kaiido都有有效的方法来完成这项工作,但他们都错过了这是一个OpenLayers问题(在Pointy的情况下,不是一个重复的问题)。

答案是这样做:

            source = new ol.source.TileWMS({
              crossOrigin: 'anonymous'
            });

基本上你必须告诉地图和你想要的图层crossOrigin:匿名。否则你的画布仍然会被污染。你知道的越多!