H5 Canvas绘制图片失败报错后,寻找问题所在以及解决问题的艰苦奋斗之路

背景

开发项目过程中遇到一个问题,H5端使用canvas绘制合成图片并导出上传,在微信开发者工具上调试,流程完美通过无不良反应,绘制的canvas能正常导出合成的图片,心中一片祥和,想着终于能下班吃饭了,然而拿到Chrome浏览器一测试,流程居然跑不通了,完了饭又吃不上了,那就继续解决问题呗。

Canvas绘制图片流程

简单说一下我在canvas上绘制图片的过程(注意我是说我使用的方法,还有别的方法方式,全凭个人喜好):

  1. 因为用的图片是保存在阿里云oss服务器上面的图片,所以在页面上有个隐藏的容器放置需要用到的图片 img 标签,图片onload加载完成后绘制到canvas上才有效果,不然画上去也是空白的不用我说了吧,因此我是等到图片都触发onload后才调用绘制canvas的方法;
  2. 绘制图片时从Dom中获取到对应图片的 img 标签;
  3. 使用canvas的drawImage API把图片绘制到canvas上;
  4. 绘制方法执行完成后把这个canvas导出为图片,并上传到服务器,至此流程结束。

流程失败原因

为什么在微信开发者工具上面测试好好的,放到Chrome浏览器就不行了呢,F12打开调试一看,报错了:Failed to execute ‘toDataURL’ on ‘HTMLCanvasElement’: Tainted canvases may not be exported.
报错信息
凭借我小学水平的英语,我隐隐看出了这个错误的意思大概是:canvas无法执行toDataURL方法,受污染的画布无法输出。

发现报错了第一时间肯定是复制粘贴到某度搜索一番,发现原来是受限于CORS 策略,会存在跨域问题,页面虽然可以使用跨域的图片(比如使用img标签或者append到页面上),因为浏览器本身不会有跨域问题,但是一旦绘制到canvas上就会污染这个canvas,导致无法提取到这个canvas的数据,也就无法输出了。

前面忘说了项目的域名和图片服务器不是同一个域名,至于本地调试就更加不可能是同一域名了(不会有人打我脸吧,不会吧不会吧),而MDN上对使用其他域名下的图片是这样写的:
MDN参考
有解决方法一切都好说,那就给图片上 crossOrigin 属性!!!

关于crossOrigin属性

在这里插入图片描述
以上是MDN的解释,自己理解过来就是:

  1. 加了 crossOrigin 属性,则表明图片就一定会按照 CORS 来请求图片。而通过CORS 请求到的图片可以再次被复用到 canvas 上进行绘制。换言之,如果不加 crossOrigin 属性的话,那么图片是不能再次被复用到 canvas 上去的。
  2. 可以设置的值有 anonymous 以及 use-credentials,2 个 value 的作用都是设置通过 CORS 来请求图片,区别在于 use-credentials 是加了证书的 CORS。
  3. 如果默认用户不进行任何设置,那么就不会发起 CORS 请求。但如果设置了除 anonymous 和 use-credentials 以外的其他值,包括空字串在内,默认会当作 anonymous来处理。

流程通了但是。。。

给图片上 crossOrigin 属性,无非就两种方式,一个是在HTML标签上设置 crossOrigin 属性,一种是用JavaScript获取到元素后设置 crossOrigin 属性,一开始我是用JavaScript去设置的,为什么说一开始用,别着急慢慢看完就知道了

// 方式一,在img标签上直接添加crossOrigin属性
<img src='图片src' id='img' crossOrigin='Anonymous' />

// 方式二,获取img标签后添加crossOrigin属性
let img = document.querySelector('#img')
img.setAttribute("crossOrigin",'Anonymous')

// 方式三,网上也有文章说两边同时添加crossOrigin的
// 好像没有必要,但是试试也无所谓,万一呢
<img src='图片src' id='img' crossOrigin='Anonymous' />
……
let img = document.querySelector('#img')
img.setAttribute("crossOrigin",'Anonymous')

给图片设置完 crossOrigin 属性后再次测试,流程顺利执行完成了,生成的图片也能上传了,下班啦下班啦,我开始欢呼然后看了一眼服务器上的图片。

“嘿,黑了”
嗯?谁黑了???
你想的没错是图片黑了,同时我的脸也黑了。
上传图片直接是空白的,也不是空白,就是图片都绘制不上去,文字这些还是有的。

设置crossOrigin属性后图片跨域无法加载

设置完 crossOrigin 属性虽然流程能执行完,但是同时也报错了:Access to image at ‘网络图片路径’ from origin ‘本地调试端口’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
报错信息
这个错误就好理解,相信做前端的没谁没遇过这个问题(不会又有人打我脸吧,不会吧不会吧),大概意思就是跨域不允许访问呗,因为服务器的响应头里没有设置Access-Control-Allow-Origin或者设置的Access-Control-Allow-Origin不包含你的域名,因此不允许你跨域访问图片。

虽然不会因此卡住流程不往下走,但同时绘制图片也空白了。

把隐藏在页面的图片容器显示出来后发现图片加载失败了,细心的同学可能发现了上面我说过等图片加载完才调用绘制canvas的方法,为什么这里又说图片加载不了呢?

这个就是我说的一开始用的JavaScript去设置 crossOrigin 属性,而HTML上面的 img 标签原本是没有设置 crossOrigin 属性的,因此页面初始化的时候图片能加载,但是到了绘制的时候重新设置这个 crossOrigin 属性的时候导致图片跨域了。

这里需要注意的是:

  1. 使用 crossOrigin 属性,服务器必须加上响应头,否则客户端设置了 crossOrigin 属性也没用,会直接报跨域错误。
  2. 如果服务器已经设置了响应头还报跨域错误,那么就要考虑是不是缓存问题导致了。
    看过很多文章,有些文章有提到了缓存问题但是没给解决方案或者说不完善的解决方案,某种情况可用,其他情况又不可用;
    有些文章就是有人提了可用的解决方案但是没人说出是因为什么,也是心累。
  3. 所以这篇文章是对网上解决方案的一些总结,也是我费尽心思终于解决问题后的一点吐槽。

服务器配置响应头

关于服务器如何配置

Access-Control-Allow-Origin: *

如果服务器是自己搭建的,某度一下会有很多解决方案,这里就不在过多赘述了。

如果是和我一样用的阿里云oss存储服务的话,那么去到oss的控制台,在跨域设置这里添加一个规则就可以了,这里提个注意的点,如果同时使用的CDN加速服务的话,那么CDN这里最好也同样设置一下,不然可能CDN的跨域规则会覆盖掉oss的,或者说出现的跨域问题是由CDN这里造成。
在这里插入图片描述
一般来说这里设置好,问题基本上都可以解决了,但是如果以上步骤都做了还是提示跨域错误,那么真相只有一个!!!就是缓存问题(打不了脸,相信我)

缓存会导致跨域?

缓存当然不会导致跨域,想啥呢。但是在我这里确确实实出现了,到底是为什么呢?

在这里先上解决方案,就是网络图片路径后面加随机数或时间戳,没想到吧哈哈哈,原因后面再讲。
这里给网络图片路径加随机数和时间戳的方式见仁见智,框架不同代码写法也不同,就不较真了,大家理解就行。

// 方式一,推荐
<img src='图片src?v=随机数或时间戳' id='img' crossOrigin='Anonymous' />

// 方式二,在我的代码里面不推荐这种方式
// 因为和我已有的逻辑冲突,图片onload完才绘制,绘制时重新加随机数又会重新触发onload,死循环
// 大家结合代码逻辑选择喜欢的方案,记得图片onload完才绘制图片哦,上面说过了
let img = document.querySelector('#img')
img.src = '图片src' + '?v=' + Math.random()
img.setAttribute("crossOrigin",'Anonymous')

为什么加了随机数或者时间戳就又可以了呢?
在这里卡了我很久,明明服务器也设置允许跨域了,为什么还是不行?
冥思苦想了很久,想到某篇文章上面有人提了加随机数的解决方案,就试了一下,居然可以了!!!
然后又想到某篇文章有人提过缓存问题(这篇文章说的解决方案是HTML img标签和JavaScript上都设置 crossOrigin 属性,我也试了但是没有用),突然思路就接上了。。。

原因在于,虽然我有一个专门放置需要绘制的图片的容器,但在页面其他地方也同样会用到这些图片,我猜想是不是别的地方缓存了同样的图片。
因为使用 img 标签加载的图片,浏览器默认会缓存起来(但我发现在我的代码里是图片加载完才调用绘制canvas的方法,因此同样会缓存,无论别的地方有没有用到),等到我使用JavaScript设置图片 crossOrigin 属性的时候,意味着图片将以 CORS 的方式请求,但缓存中的图片显然不是的,所以浏览器直接就拒绝了,连网络请求都没有发起,容器里面的图片也就变成了图片加载失败的样子了。
图片加载失败
在 Chrome 的调试器中,在 network 面板中,勾选了 disable cache 选项,验证了问题确实是因为缓存,浏览器这时发起了请求并且JavaScript的 img 也能正常请求到图片。

所以后面我改用了方式一,在 img 标签上设置 crossOrigin 属性,再测试一切正常,终于可以下班了!!!

突然想起

一开始在微信开发者工具上面调试完全没问题,这就不禁让人想到,微信开发者工具对跨域这个问题上,有些过于宽松了,起码比移动端的微信浏览器都宽松,因为用真机测试同样会出现跨域,这不是坑害我们这些开发者吗,连在移动端微信浏览器的体验都不一致。手动狗头!!!

想和大家说

写博客的初衷呢,是把自己遇到的问题和值得分享的解决方法分享给大家,希望这篇文章可以帮到遇到同样问题的程序猿们。

最后附上MDN关于canvas绘图的参考文献

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值