解决canvas图片getImageData,toDataURL跨域问题

这篇文章发布于 2018年02月10日,星期六,23:27,归类于 Canvas相关。 阅读 116287 次, 今日 18 次 19 条评论

 

一、首先,图片服务器需要配置Access-Control-Allow-Origin

一般团队都会有一个专门域名放置静态资源,例如腾讯是gtimg.com,百度是bdimg.com;或者很多团队使用的是腾讯云或者阿里云的服务。

而主页面所在域名往往不一样,当需要需要对canvas图片进行getImageData()toDataURL()操作的时候,跨域问题就出来了,而且跨域问题还不止一层。

首先,第一步,图片服务器需要配置Access-Control-Allow-Origin信息,例如:

如PHP添加响应头信息,*通配符表示允许任意域名:

header("Access-Control-Allow-Origin: *");

或者指定域名:

header("Access-Control-Allow-Origin: www.zhangxinxu.com");

此时,Chrome浏览器就不会有Access-Control-Allow-Origin相关的错误信息了,但是,还会有其他的跨域错误信息。

二、canvas图片getImageData cross-origin跨域问题

对于跨域的图片,只要能够在网页中正常显示出来,就可以使用canvas的drawImage() API绘制出来。但是如果你想更进一步,通过getImageData()方法获取图片的完整的像素信息,则多半会出错。

举例来说,使用下面代码获取github上的自己头像图片信息:

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

var img = new Image();
img.onload = function () {
    context.drawImage(this, 0, 0);
    context.getImageData(0, 0, this.width, this.height);
};
img.src = 'https://avatars3.githubusercontent.com/u/496048?s=120&v=4';';

结果在Chrome浏览器下显示如下错误:

Uncaught DOMException: Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.

出错信息截图

Firefox浏览器错误为:

SecurityError: The operation is insecure.

如果使用的是canvas.toDataURL()方法,则会报:

Failed to execute ‘toDataURL’ on ’HTMLCanvasElement’: Tainted canvased may not be exported

原因其实都是一样的,跨域导致。

那有没有什么办法可以解决这个问题呢?

可以试试crossOrigin属性。

三、HTML crossOrigin属性解决资源跨域问题

在HTML5中,有些元素提供了支持CORS(Cross-Origin Resource Sharing)(跨域资源共享)的属性,这些元素包括<img><video><script>等,而提供的属性名就是crossOrigin属性。

因此,上面的跨域问题可以这么处理:

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

var img = new Image();
img.crossOrigin = '';
img.onload = function () {
    context.drawImage(this, 0, 0);
    context.getImageData(0, 0, this.width, this.height);
};
img.src = 'https://avatars3.githubusercontent.com/u/496048?s=120&v=4';';

增加一个img.crossOrigin = ''即可,虽然JS代码这里设置的是空字符串,实际上起作用的属性值是anonymous

crossOrigin可以有下面两个值:

关键字 释义
anonymous 元素的跨域资源请求不需要凭证标志设置。
use-credentials 元素的跨域资源请求需要凭证标志设置,意味着该请求需要提供凭证。

其中,只要crossOrigin的属性值不是use-credentials,全部都会解析为anonymous,包括空字符串,包括类似'abc'这样的字符。

例如:

img.crossOrigin = 'abc';
console.log(img.crossOrigin);    // 结果是'anonymous'

crossOrigin解析为anonymous

另外还有一点需要注意,那就是虽然没有crossOrigin属性,和设置crossOrigin="use-credentials"在默认情况下都会报跨域出错,但是性质上却不一样,两者有较大区别。

crossOrigin兼容性

IE11+(IE Edge),Safari,Chrome,Firefox浏览器均支持,IE9和IE10会报SecurityError安全错误,如下截图:

四、crossOrigin属性为什么可以解决资源跨域问题?

crossOrigin=anonymous相对于告诉对方服务器,你不需要带任何非匿名信息过来。例如cookie,因此,当前浏览器肯定是安全的。

就好比你要去别人家里拿一件衣服,crossOrigin=anonymous相对于告诉对方,我只要衣服,其他都不要。如果不说,可能对方在衣服里放个窃听器什么的,就不安全了,浏览器就会阻止。

五、IE10浏览器不支持crossOrigin怎么办?

我们请求图片的时候,不是直接通过new Image(),而是借助ajax和URL.createObjectURL()方法曲线救国。

代码如下:

var xhr = new XMLHttpRequest();
xhr.onload = function () {
    var url = URL.createObjectURL(this.response);
    var img = new Image();
    img.onload = function () {
        // 此时你就可以使用canvas对img为所欲为了
        // ... code ...
        // 图片用完后记得释放内存
        URL.revokeObjectURL(url);
    };
    img.src = url;
};
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.send();

此方法不仅IE10浏览器OK,原本支持crossOrigin的诸位浏览器也是支持的。

也就多走一个ajax请求,还可以!

根据,根据实践发现,在IE浏览器下,如果请求的图片过大,几千像素那种,图片会加载失败,我猜是超过了blob尺寸限制。

六、结束语

最近工作中学到的一点小经验,希望可以帮到遇到类似问题的小伙伴。

感谢阅读!

(本篇完)

分享到:


发表评论(目前19 条评论)

  1. 北海舞说道:

    本地canvas从video获取像素不行。

  2. 南京韶邵说道:

    实在不行,nginx里面开一个代理,或者后端做一个代理接口,把跨域都干掉

  3. moonslade说道:

    在chrome测试,不管anonymous还是ajax,都会携带Origin去请求,只要对面图片的Access-Control-Allow-Origin设置的不是本页面,就会跨域。也就是说该不允许的还是不允许

  4. Logic说道:

    在IOS12上好像不行
    无论怎么设置,只要toDataURL, 都会报错

  5. 唐竟成说道:

    文章中关于 crossOrigin 属性:anonymous 和 use-credentials 没有解释清楚。

    anonymous

    从翻译角度来看是 匿名 的意思,表示不携带任何用户信息去获取资源,只会通过http头数据校验数据是否可用。

    技术要点:请求中不会携带用户信息,例如cookie。请求头中会有origin数据,需要与响应头的 access-control-allow-origin 字段值相匹配。

    use-credentials
    字面意思为使用凭证,表示请求会携带用户信息,服务端会校验请求凭证信息并且发送给浏览器,浏览器根据响应信息,判断是否采用响应数据。

    技术要点:请求中携带用户信息(例如,cookie)。请求头中会有origin数据。响应头中会有 access-control-allow-origin 数据,该数据必须是具体的域名,不能为通配符*,需要与请求头的origin相匹配。响应头中的 access-control-allow-credentials 需要为 true ,表示允许请求中携带凭证信息(例如,cookie);其他情况下,不符合要求,浏览器将不会把响应内容返回给请求的发送者。

    • moonslade说道:

      对,跨域问题本质上是为了避免csrf风险,只要不允许带cookie等用户信息就可以跨域,anonymous所谓的匿名其实是指不携带用户侧的信息,而不是服务器侧的信息,所以文章里说的有失偏颇。

  6. 晓东说道:

    安卓11 :1,2步都设置了,但是还是无法调用canvas.ToDataURL。有人碰到相同情况吗?ios和一部安卓10的手机没有问题

  7. ougege说道:

    尝试了一下,http的图片可以,但是对https图片还是报错,我用的百度的logo
    https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png

  8. 小小生说道:

    亲测,IE11也存在跨域问题

  9. tomato说道:

    一开始把crossOrigin设置在img标签内 发现不行
    后来试了 new Image() 的写法可以了
    貌似必须使用img.crossOrigin = ‘anonymous’这种写法

  10. 将图片服务器上的图片进行标记之后转换为base64编码,但是出现Uncaught DOMException: Failed to execute ‘toDataURL’ on ‘HTMLCanvasElement’: Tainted canvases may not be exported.错误!

  11. go_ahead说道:

    Safari还是不行啊 出现这个错误 The operation is insecure. 如果不行的话 那不是ios手机上就不能使用了。

  12. jetango说道:

    非常赞,很是感谢

  13. 说道:

    很不错

  14. 刘昭廷说道:

    每天关注鑫大神的空间,自己的经验每天都会涨一点~

  15. 敖爽说道:

    虽然有crossOrigin这个属性 , 但是要达到正常的getImageData还是需要服务器那边支持跨域才行吧~ , 就像上一篇文章里说的那样

  16. 小白说道:

    前辈你好,最近在做audio的canvas频谱图,
    //连接方式:source → analyser → destination
    遇到的问题就是使用本地static音频文件的时候正常,用网上的连接就会报跨域问题,我找了一些资料,他们给的解决也是crossorigin,但是设置之后就被403了(谷歌也是版本皆为最新)
    火狐报错:
    已拦截跨源请求:同源策略禁止读取位于 http://m10.music.126.net/20180211152619/984afca11628ed48d15703a6b9f83383/ymusic/1370/452a/f846/a98bb4e0b0374d5b25c0694c534d5056.mp3 的远程资源。(原因:CORS 头缺少 ‘Access-Control-Allow-Origin’)。
    没有设置crossorigin的情况
    传递到 createMediaElementSource 的 HTMLMediaElement 有一个跨源资源,该节点将输出无声。