所以我正在尝试为我正在构建的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);
};
};
答案 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:匿名。否则你的画布仍然会被污染。你知道的越多!