canvas图片跨域问题

首先,这个问题,我们在开发的时候,也遇到过此问题,在里看到了,这个解释,非常好,下面在这里,做个备忘,如果导致抄袭,望谅解

引用:https://www.jianshu.com/p/c3aa975923de         我加了一些自己的东西,在下边

报错问题显示:

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

'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这是我在测试的时候发布的问题,只要图片路径是 localhost/Uploads/1.jpg 而服务器的地址则是  localhost:1024 这种,则会显示此问题

我的解决思路:

写demo的时候我用的本地图片,调canvas toDataURL方法并没有报错


canvas图片跨域问题_第1张图片

但是在联调的时候,换成外域图片,却报错了:

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

我找到答案是这样的,让我给图片加一个属性

varimg =newImage();

img.setAttribute('crossOrigin','anonymous');

img.src = url;

当时没想那么多,加进去试试再说,不出意料地解决了问题

然而在加了图片预加载代码之后,发现有的图片就加载不出来了,打开控制台报错:


上面简单地说了下我遇到问题与解决问题

先说说 Tainted canvases may not be exported 的问题。对于外域图片,浏览器仍然是允许你画到canvas上的,但是toDataURL就会报错(toBlob也是)。为什么会这样呢?

This protects users from having private data exposed by using images to pull information from remote web sites without permission.

大意就是说:如果你请求外域的图片without permission,可能会暴露你的隐私数据,所以浏览器为了保护你的隐私会限制这样的请求。

「why?请求外域图片怎么就会暴露我的隐私数据了?」其实我也不明白,这个坑请先自己填一下,之后会补充。

那么怎么绕过浏览器的「关照」呢?答案是:你允许就行了~而img.setAttribute('crossOrigin', 'anonymous');就是告诉浏览器,我允许!

再说说'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.


这个异常实际上在控制台里是拿不到调用栈的,浏览器并不会告诉你是这里出了问题

这个异常信息本身是说「reponse header中不带Access-Control-Allow-Origin(以下简称AC)这个字段,所以'xxx'被同源策略阻止了」。

(如果你想进一步了解同源策略,可以看看阮老师的这篇文章。)

这时候你可能会想起,我之前不加img.setAttribute('crossOrigin', 'anonymous');,也去请求外域图片,怎么就没报过错?

这里我简单补充一下:img.setAttribute('crossOrigin', 'anonymous');,加了这句,就意味着你这次的图片请求变成了CORS请求,就要受同源策略的限制了(而这个报错就说明你受到了浏览器同学的关怀:D)。

其实因果关系是这样的:img.setAttribute('crossOrigin', 'anonymous');会让request header加上Origin字段,从而变成了一个CORS请求


canvas图片跨域问题_第2张图片
(如果你想进一步了解CORS,可以看看阮老师的这篇文章。)

回到正题,既然问题是response header中不带AC,那让服务端返回应该就可以了吧?

如果服务端真的没有配置CORS,那先让他们配置好。

但是,即使配置了,仍然可能存在问题。

在我遇到的情况里,其实服务端是做了配置的,那谁来背锅?

我用的是apache +php后端  前端用的是vue

我后台apache httpd-vhost中去增加虚拟域名,我的mac 用的是mamp apache端口用的是8888的,所以我这么设置 支持跨域 在host文件中 

canvas图片跨域问题_第3张图片

好你的127.0.0.1 的指向那虚拟域名等等,但是可能还是依然没解决问题,此时,你还需要对apache的配置文件进行设置支持跨域,你的apache配置文件 httpd.conf 肯定有一个对你项目目录的指向的一大坨,他应该是这样儿的


canvas图片跨域问题_第4张图片

设置完这些后,要重启apache。

此时,你可能还需要设置一下你的  /etc/host 文件   前边说了,我的localhost   用的是8888端口   

那么,在host 文件中 加入一行   127.0.0.1:8888  localhost

如果你的apache 端口号是3306,则不需要改host ,修改host文件后,要重启电脑 ,否则不会生效的。

重新进入浏览器中,资源已经可以支持跨域了,大功告成!

下面是,ajax时进行的跨域设置:

我用的是tp5  我不管,下一个遇到此问题的人,你的header写哪,但是有个位置控制这些,你要这么设置你的头信息,让其支持ajax 的跨域,其次,就是前端jsonp跨域的事儿了,后台的锅就没啦!~

canvas图片跨域问题_第5张图片

然后,前端处理先清掉缓存,由后端直接把图片资料转成base64的码,再由前端获取,使用以下函数调用canvas进行操作,生成的新图片,再用base64格式传回服务器,由php解析成图片,保存。

首先,第一锅要给浏览器缓存

这里先赘述一下:我们第一次访问一个页面时,会发现图片会慢慢加载出来;当我们再次访问同一个页面时,会发现图片很快就加载出来了。主要就是因为浏览器第一次已经把图片缓存下来了,第二次不需要再从服务端请求,而直接从缓存里取。

虽然方便了,但这可能引发其它问题。上面提到过,原先的图片预加载代码有问题,简化版如下:

varimg;for(variinimages){  img =newImage();  img.src = images[i].url;}

注意,这段代码没带img.setAttribute('crossOrigin', 'anonymous');。其实本质上并不是因为没带这句才出的问题,跟实际的场景有关

当时的场景是:图片预加载先行;然后编译第一个涂鸦板,之后选中其它的涂鸦板再编译该涂鸦板;每个涂鸦板编译的时候也会去发送图片请求(CORS请求)。

问题的现象是:第一个涂鸦板的图片加载出来了,后面几个都没加载出来。

why?

对于第一张图片,两个请求(来自预加载和涂鸦板编译)几乎是同时发送的;而其它几张图片,都是预加载在先,编译在后。如此,在编译其它几个涂鸦板时,浏览器会直接取缓存里取图片。

而我们预加载时发送的是普通请求,这意味着这些请求的response不会带AC(不是必然的,取决于服务端怎么做):

canvas图片跨域问题_第6张图片

普通请求

canvas图片跨域问题_第7张图片

CORS请求

所以,当其它涂鸦板编译时,发出的是CORS请求,拿到的却是不带AC的response,结果必然出错。

这里我得再强调一下,并不是普通请求的response就一定不带AC,这个取决于服务端怎么处理。比如像请求七牛公共空间的图片,不管是普通请求还是CORS请求,都会带AC。

知道原理之后解决问题就简单了,先清清缓存,然后加上crossOrigin

varimg;for(variinimages){  img =newImage();  img.setAttribute('crossOrigin','anonymous');  img.src = images[i].url;}

So,到此为止?No,我们有请第二位背锅先生:CDN缓存

上面提到过,我们的图片域名由源站改为了CDN。

先还原一下当时的场景:

有一位老师用涂鸦板批改作业,当她保存的时候发现保存不了(这是另一个无关的问题,不赘述),就请QA哥哥帮忙。QA哥哥打开控制台......(省略一万字),然后在一个新tab里打开了一张图片。当他再回到原页面时,一刷新,发现这张图片没了。当时我就跪地上了。。。

我是束手无策了,于是找了CDN的gg们帮忙。他们说的确存在这种问题,正在修复中。。

在进一步讲之前,结合我的手残图,先普及几个CDN相关的知识:

canvas图片跨域问题_第8张图片

CDN会缓存response,源站不会。

CDN接收到请求时,如果没有缓存,会将请求发送到源站,将结果回传给请求端,并且缓存结果(response),简称回源。

CDN是根据url进行缓存的,比如你请求一次http://a.b.c/1.jpg,之后再请求相同的url,那你拿到的是缓存下来的response;如果你加了个参数比如http://a.b.c/1.jpg?100,这个时候就会回源,但是并不会破坏掉http://a.b.c/1.jpg对应的缓存。

以上3点只是我们这边的情况,也许有特殊性。

现在可以简单理理,这是个怎样的问题:

老师的图片本来是可以加载到的,并且在没「打开图片」之前,都是发送的CORS请求(在涂鸦板预加载和编译时发送),这些CORS请求的response早已在A节点缓存了下来。

而打开这张图片,意味着一次普通请求,奇怪的是,请求去到了B节点,而B节点尚未缓存,所以进行了回源。

而刷新页面后,请求虽然是CORS请求,但是却又走到了B节点,结果就是:一个CORS请求拿到一个普通请求的response,浏览器由于同源策略而报错。

(正常情况下,如果一开始去到A节点,那么应该一直都是去A节点。)

嗯,道理明白了。那除了等gg们修复问题,还有什么解决办法吗?

我猜你已经想到了:加随机数。

最终的做法是在图片onerror的时候带随机数(比如时间戳)重发请求,大概就是:

functionrequestImg(src){varimg =newImage();  img.src = src;  img.onerror =function(){vartimeStamp = +newDate();    requestImg(src+'?'+timeStamp);  }}

总结

总得来说,当你遇到这两个问题的时候,需要做两件事:

img.setAttribute('crossOrigin', 'anonymous');

图片请求失败时,带随机数重发请求。

你可能感兴趣的:(canvas图片跨域问题)