一、背景
项目中遇到一个问题,同一个图片在 dom 节点中使用了 'img' 标签来加载,同时由于项目使用了 ThreeJS 3D 渲染引擎,在加载纹理时使用了 TextureLoader 来加载了同一张图片,而由于图片是在阿里云服务器上的,所以最后报出了如下错误,意思是在访问图片时出现了跨域问题:
二、问题梳理
2.1 关于图片的加载
图片是来自于阿里云服务器的,和本地 localhost 必然存在跨域问题。通过 dom 节点的 'img' 标签来直接访问是没有问题,因为浏览器本身不会有跨域问题。问题出在通过 TextureLoader 来加载图片时出现了跨域问题。查看了 TextureLoader 的源码,发现其进一步使用了 ImageLoader 来加载图片,加载图片的代码大致如下:
crossOrigin: 'anonymous',
......
var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
......
if ( url.substr( 0, 5 ) !== 'data:' ) {
if ( this.crossOrigin !== undefined )
image.crossOrigin = this.crossOrigin;
}
......
image.src = url;
这段代码所描述的大致思路是:
- 通过JS代码,创建一个 img 的 dom element,然后使用这个 element 来加载图片。
- 默认情况下,设置了 crossOrigin 的跨域属性为 'anonymous'。
所以,问题的关键在于,同一张图片,先用 'img' 标签去加载了,然后再在 JS 代码中,创建一个 'img' 并且设置了 crossOrigin 的跨域属性为 'anonymous',那么在 JS 中创建的 'img' 就会出现访问图片而产生跨域的问题。
2.2 关于 crossOrigin
关于 crossOrigin,我们看看 MDN 的解释。
这段话,用我自己的理解来解释一下:
- 加了 crossorigin 属性,则表明图片就一定会按照 CORS 来请求图片。而通过CORS 请求到的图片可以再次被复用到 canvas 上进行绘制。换言之,如果不加 crossorigin 属性的话,那么图片是不能再次被复用到 canvas 上去的。
- 可以设置的值有 anonymous 以及 use-credentials,2 个 value 的作用都是设置通过 CORS 来请求图片,区别在于 use-credentials 是加了证书的 CORS。
- 如果默认用户不进行任何设置,那么就不会发起 CORS 请求。但如果设置了除 anonymous 和 use-credentials 以外的其他值,包括空字串在内,默认会当作 anonymous来处理。
2.3 问题总结
通过前面 2 点的梳理,我们得出如下结论:
- 通过 'img' 加载的图片,浏览器默认情况下会将其缓存起来。
- 当我们从 JS 的代码中创建的 'img' 再去访问同一个图片时,浏览器就不会再发起新的请求,而是直接访问缓存的图片。但是由于 JS 中的 'img' 设置了 crossorigin,也就意味着它将要以 CORS 的方式请求,但缓存中的图片显然不是的,所以浏览器直接就拒绝了。连网络请求都没有发起。
- 在 Chrome 的调试器中,在 network 面板中,我们勾选了 disable cache 选项,验证了问题确实如第 2 点所述,浏览器这时发起了请求并且 JS 的 'img' 也能正常请求到图片。
三、解决问题
前面通过勾选 disable cache 来避免浏览器使用缓存图片而解决了问题,但实际用户不会这样使用啊。根据前面的梳理,'img' 不跨域请求,而 JS 中的 'img' 跨域请求,所以不能访问缓存,那么是不是可以将 JS 中的 'img' 也设置成不跨域呢,于是将 JS 中的 'img' 的 crossorigin 设置为 undefine,结果图片是可以加载了,但又得到如下错误。
这段错误的意思是,这一个来自于CORS 的图片,是不可以再次被复用到 canvas 上去的。这就验证了关于 crossorigin 中的第 1 点。
既然 'img' 和 JS 中的 'img' 都不加 crossorigin不能解决 canvas 重用的问题,那么在两边同时都加上 crossorigin 呢?果然,在 'img' 中和 JS 中的 'img' 都加上 crossorigin = "anonymous",图片可以正常加了,同时也可以被复用到 'canvas' 上去了。
另外,需要注意的 2 个小问题是:
- 服务器必须加上字段,否则,客户端设置了也是没用的。
Access-Control-Allow-Origin: *
- 如果是已经出了问题,你才看到这篇文章,或者才去想到这么解决。那么要记得先清理一下游览器所缓存的图片。否则你就会发现,有的图片可以访问,而有的不可以。那是因为缓存中之前存储了未 CORS 的图片。
四、总结
前面说了一框,只是想把这个过程完整的记录下来。整个问题的总结是:
- 同一张图片或者同一个地址,同时被 'img' 所访问,而随后后又会被如 JS 中去访问。而图片存储的地址是跨域的,那么就可能因为缓存问题而导致 JS 中的访问出现跨域问题。
- 解决的办法是让 'img' 标签和 JS 中的访问都走跨域访问的方式,这样既可以解决跨域访问的问题,也可以解决跨域图片在 canvas 中的复用。
- 设置crossOrigin为空