一、背景
項(xiàng)目中遇到一個(gè)問(wèn)題,同一個(gè)圖片在 dom 節(jié)點(diǎn)中使用了 <img> 標(biāo)簽來(lái)加載,同時(shí)由于項(xiàng)目使用了 ThreeJS 3D 渲染引擎,在加載紋理時(shí)使用了 TextureLoader 來(lái)加載了同一張圖片,而由于圖片是在阿里云服務(wù)器上的,所以最后報(bào)出了如下錯(cuò)誤,意思是在訪問(wèn)圖片時(shí)出現(xiàn)了跨域問(wèn)題:

二、問(wèn)題梳理
2.1 關(guān)于圖片的加載
圖片是來(lái)自于阿里云服務(wù)器的,和本地 localhost 必然存在跨域問(wèn)題。通過(guò) dom 節(jié)點(diǎn)的 <img> 標(biāo)簽來(lái)直接訪問(wèn)是沒(méi)有問(wèn)題,因?yàn)闉g覽器本身不會(huì)有跨域問(wèn)題。問(wèn)題出在通過(guò) TextureLoader 來(lái)加載圖片時(shí)出現(xiàn)了跨域問(wèn)題。查看了 TextureLoader 的源碼,發(fā)現(xiàn)其進(jìn)一步使用了 ImageLoader 來(lái)加載圖片,加載圖片的代碼大致如下:
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;
這段代碼所描述的大致思路是:
- 通過(guò)JS代碼,創(chuàng)建一個(gè) img 的 dom element,然后使用這個(gè) element 來(lái)加載圖片。
- 默認(rèn)情況下,設(shè)置了 crossOrigin 的跨域?qū)傩詾?'anonymous'。
所以,問(wèn)題的關(guān)鍵在于,同一張圖片,先用 <img> 標(biāo)簽去加載了,然后再在 JS 代碼中,創(chuàng)建一個(gè) <img> 并且設(shè)置了 crossOrigin 的跨域?qū)傩詾?'anonymous',那么在 JS 中創(chuàng)建的 <img> 就會(huì)出現(xiàn)訪問(wèn)圖片而產(chǎn)生跨域的問(wèn)題。
2.2 關(guān)于 crossOrigin
關(guān)于 crossOrigin,我們看看 MDN 的解釋。
crossOrigin
這段話,用我自己的理解來(lái)解釋一下:
- 加了 crossorigin 屬性,則表明圖片就一定會(huì)按照 CORS 來(lái)請(qǐng)求圖片。而通過(guò)CORS 請(qǐng)求到的圖片可以再次被復(fù)用到 canvas 上進(jìn)行繪制。換言之,如果不加 crossorigin 屬性的話,那么圖片是不能再次被復(fù)用到 canvas 上去的。
- 可以設(shè)置的值有 anonymous 以及 use-credentials,2 個(gè) value 的作用都是設(shè)置通過(guò) CORS 來(lái)請(qǐng)求圖片,區(qū)別在于 use-credentials 是加了證書(shū)的 CORS。
- 如果默認(rèn)用戶不進(jìn)行任何設(shè)置,那么就不會(huì)發(fā)起 CORS 請(qǐng)求。但如果設(shè)置了除 anonymous 和 use-credentials 以外的其他值,包括空字串在內(nèi),默認(rèn)會(huì)當(dāng)作 anonymous來(lái)處理。
2.3 問(wèn)題總結(jié)
通過(guò)前面 2 點(diǎn)的梳理,我們得出如下結(jié)論:
- 通過(guò) <img> 加載的圖片,瀏覽器默認(rèn)情況下會(huì)將其緩存起來(lái)。
- 當(dāng)我們從 JS 的代碼中創(chuàng)建的 <img> 再去訪問(wèn)同一個(gè)圖片時(shí),瀏覽器就不會(huì)再發(fā)起新的請(qǐng)求,而是直接訪問(wèn)緩存的圖片。但是由于 JS 中的 <img> 設(shè)置了 crossorigin,也就意味著它將要以 CORS 的方式請(qǐng)求,但緩存中的圖片顯然不是的,所以瀏覽器直接就拒絕了。連網(wǎng)絡(luò)請(qǐng)求都沒(méi)有發(fā)起。
- 在 Chrome 的調(diào)試器中,在 network 面板中,我們勾選了 disable cache 選項(xiàng),驗(yàn)證了問(wèn)題確實(shí)如第 2 點(diǎn)所述,瀏覽器這時(shí)發(fā)起了請(qǐng)求并且 JS 的 <img> 也能正常請(qǐng)求到圖片。
三、解決問(wèn)題
前面通過(guò)勾選 disable cache 來(lái)避免瀏覽器使用緩存圖片而解決了問(wèn)題,但實(shí)際用戶不會(huì)這樣使用啊。根據(jù)前面的梳理,<img> 不跨域請(qǐng)求,而 JS 中的 <img> 跨域請(qǐng)求,所以不能訪問(wèn)緩存,那么是不是可以將 JS 中的 <img> 也設(shè)置成不跨域呢,于是將 JS 中的 <img> 的 crossorigin 設(shè)置為 undefine,結(jié)果圖片是可以加載了,但又得到如下錯(cuò)誤。

這段錯(cuò)誤的意思是,這一個(gè)來(lái)自于CORS 的圖片,是不可以再次被復(fù)用到 canvas 上去的。這就驗(yàn)證了關(guān)于 crossorigin 中的第 1 點(diǎn)。
既然 <img> 和 JS 中的 <img> 都不加 crossorigin不能解決 canvas 重用的問(wèn)題,那么在兩邊同時(shí)都加上 crossorigin 呢?果然,在 <img> 中和 JS 中的 <img> 都加上 crossorigin = "anonymous",圖片可以正常加了,同時(shí)也可以被復(fù)用到 <canvas> 上去了。
另外,需要注意的 2 個(gè)小問(wèn)題是:
- 服務(wù)器必須加上字段,否則,客戶端設(shè)置了也是沒(méi)用的。
Access-Control-Allow-Origin: *
- 如果是已經(jīng)出了問(wèn)題,你才看到這篇文章,或者才去想到這么解決。那么要記得先清理一下游覽器所緩存的圖片。否則你就會(huì)發(fā)現(xiàn),有的圖片可以訪問(wèn),而有的不可以。那是因?yàn)榫彺嬷兄按鎯?chǔ)了未 CORS 的圖片。
四、總結(jié)
前面說(shuō)了一框,只是想把這個(gè)過(guò)程完整的記錄下來(lái)。整個(gè)問(wèn)題的總結(jié)是:
- 同一張圖片或者同一個(gè)地址,同時(shí)被 <img> 所訪問(wèn),而隨后后又會(huì)被如 JS 中去訪問(wèn)。而圖片存儲(chǔ)的地址是跨域的,那么就可能因?yàn)榫彺鎲?wèn)題而導(dǎo)致 JS 中的訪問(wèn)出現(xiàn)跨域問(wèn)題。
- 解決的辦法是讓 <img> 標(biāo)簽和 JS 中的訪問(wèn)都走跨域訪問(wèn)的方式,這樣既可以解決跨域訪問(wèn)的問(wèn)題,也可以解決跨域圖片在 canvas 中的復(fù)用。
最后,感謝你能讀到并讀完此文章,如果分析的過(guò)程中存在錯(cuò)誤或者疑問(wèn)都?xì)g迎留言討論。如果我的分享能夠幫助到你,還請(qǐng)記得幫忙點(diǎn)個(gè)贊吧,謝謝。
