8

所以我正在尝试为我正在构建的 OpenLayers 3 应用程序创建打印地图功能。我知道他们的例子,但每当我尝试使用它时,我都会遇到可怕的受污染的画布问题。我已经阅读了整个互联网,遇到人们说首先要正确设置 CORS(完成并完成),但也要这样做:

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

上面的描述在这里

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

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

有什么想法吗?

我见过这个。我的问题是他们如何将其整合到一个有效的功能中。就像是:

    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);
     };
   };
4

2 回答 2

7

如果crossOrigin浏览器支持属性/属性(它现在在 FF、Chrome、最新的 Safari 和 Edge 中),但服务器没有以正确的标题( Access-Control-Allow-Origin: *) 进行响应,则 img 的onerror事件会触发。

因此,如果我们想绘制图像,我们可以只处理这个事件并删除属性。
对于不处理此属性的浏览器,测试画布是否被污染的唯一方法是调用toDataURLtry 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 中调用它的 toDataURL -catch 块:

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 < Edge 中,在画布上绘制 svg 会因安全问题而污染画布,同样,如果 a<foreignObject>存在于svg 绘制到画布上,最后,如果另一个受污染的画布被绘制到画布上,任何 UA 都会污染画布。

所以在这些情况下检查画布是否被污染的唯一解决方案是try-catch,最好是在 1px x 1px 测试画布上这样做。

于 2016-01-07T02:31:25.387 回答
5

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

答案是这样做:

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

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

于 2016-01-07T19:11:37.207 回答