使用js创建img加载阿里云oss图片跨域的问题
一、项目背景
我们项目有个场景是需要前端将部分页面内容转成图片传给后端使用,这里我们用的是html-to-image 插件,之前有写过一篇介绍这个插件,详情可以移步这里🚩
,本文算是这一篇的续作。
在上一篇的文章中我们使用js去创建一个image标签然后加载网络图片然后添加到容器中,再将容器去生成一个图片。我们有两个容器,这里称为容器A和容器B,容器A内部的图片是后端预设的一些图片,容器B的图片是用户上传到oss的图片。上述的操作过程中容器A是没问题的,容器B的现象很是奇怪,用户刚上传的图片去处理转图片是正常的,但是过一段时间后转图片就失败了,用户之前上传的图片同样转换失败。
二、问题分析
1.授权时间问题
根据现象来说,第一反应是oss的授权过期问题,因为我们使用oss的AccessKey去上传的图片,所以我在想是不是阿里云那边有限制,当授权过期了以后,创建img加载阿里云的图片失败了。
但是页面中的图片加载是正常的,直接在浏览器访问图片的url也是可以直接跳下载的,所以大概率不是这个的问题。
2.是否是跨域?
因为图片加载跨域这个是很常见的问题了,所以我在想是不是跨域的问题,但是控制台并没有错误提示,仅有toblob
方法抛出的错误,并没有跨域的错误,所以我就排除了跨域的问题,认为还是toBlob内部方法的问题。然后我就开始了分析错误、调试源码等一系列手段,然并卵。

在我试过各种办法后依然无能为力以后,我决定整个链路检查一下各个环节,看一看到底是不是插件的问题,首先就是看图片是否正常加载,于是我给图片加了一个onerror
事件:
JavaScript
imgNode.onerror = function (e) {
console.log(e)
}
然后这个事件就水灵灵的触发了,说明还是图片加载的问题,看来是错怪html-to-image
了。然后我就看到了控制台如下的错误:
看来还是跨域的问题,那为啥之前控制台为啥不报错呢,看了下其实是报错了的,现在想来也不知道是当时过滤了还是错误信息太多忽略了,又或者是认为是插件问题就没仔细看报错信息,反正就是白忙活了半天。

三、解决方案
既然知道是跨域问题就好说了,因为我们阿里云的跨域设置的全部允许,所以肯定是客户端这边跨域处理有问题。
在img标签中有一个属性**crossorigin
,该属性有两个值,一个是anonymous
一个是use-credentials
,简单来说这两个都是发送跨域请求的,anonymous发送的是没有凭据的跨域请求,use-credentials发送的是有凭据的跨域请求,如果设置有设置 crossorigin**属性,则一定是会发送跨域请求的,因为无效值一律当做anonymous
。那我们就加上这个属性试试:
js
imgNode.crossOrigin = 'anonymous'
现在我们设置了img标签的crossorigin属性后重试,得到的依然是错误,这是为何?同时为什么页面中的图片是能正常加载的,而将页面中的图片处理后转图片就会跨域?接下来我们把目光转向浏览器控制台:
可以看到,第一次进入页面时加载的图片都是缓存了的,此时我们打开控制台的disable cache 选项强制走网络加载不走缓存。
非常好,跨域请求消失了,取而代之的是正常的请求和正确的图片转换。但是很明显,我们不能让用户自己去打开这个选项,所以接下来我们捋一捋思路制定解决办法:
- 首先页面中的img标签第一次加载图片,此时图片正常加载显示
- 因为第一次加载以后,浏览器缓存了图片资源,此时我们使用js创建img标签,因为url一致所以命中了之前的缓存。此时可能因为是之前第一次加载图片的时候img标签没有使用crossorigin属性,所以并没有缓存跨域信息。
- 在使用js创建img标签加载图片时可能因为Chrome的默认行为或者别的限制导致该图片要使用cors的方式去加载,但是因为命中的缓存没有跨域信息所以此时浏览器认为这是一个不合法的跨域请求然后直接拦截掉了。
针对上面的分析我们可以给出两种解决方案:
1.全部图片加载都使用crossorigin属性携带跨域信息
既然第一次加载图片的时候没有携带跨域信息导致后面命中缓存的时候因为没有跨域信息报错,那么我们从根源出发,确保网络请求图片资源的时候都是携带跨域信息的。
jsx
<img src={item.url} width={'100%'} crossOrigin='anonymous' />
2.不命中缓存
如果项目中使用图片的地方比较多,一个一个加的话肯定很麻烦,这时还有一种办法,既然是因为命中缓存导致无法发起跨域请求那我们可以强行不命中缓存,我们给url加上一个时间戳的请求参数:
js
imgNode.src = imgUrl + '?t=' + Date.now()
这样因为url因为每次都不一样所以此时加载图片无法命中缓存会转而请求网络资源,此时也是可以正常加载的。
实测两种方式都可以解决问题,大家选择合适的方法即可。
四、总结复盘
上面的问题排查虽然比较曲折,其实说到底就是一个跨域问题,如果细心些应该能早些发现这个问题。所以我总结了一下本次问题的重点:
- 对于问题的查找不能单纯基于现象去定位,要对问题的发生链路要有整体思考,否则很容易陷入茫然,会认为bug非常奇怪无从下手。
- 平时开发中解决跨域问题都是服务器设置响应头为
Access-Control-Allow-Origin:*
来解决,但是在本文中阿里云设置了该响应头后依然会出现跨域问题,原因就是资源的缓存问题。和传统的接口开发不一样,资源在网络加载以后是会有缓存的,而缓存携带的信息缺失会导致浏览器认为请求的不合理从而直接阻止掉资源加载。 - 使用js去加载网络图片时,需要考虑到浏览器的一些默认行为及限制,特别是需要考虑到缓存的影响。