一個關於image訪問圖片跨域的問題
一、背景
專案中遇到一個問題,同一個圖片在 dom 節點中使用了 <img> 標籤來載入,同時由於專案使用了 ThreeJS 3D 渲染引擎,在載入紋理時使用了 TextureLoader 來載入了同一張圖片,而由於圖片是在阿里雲伺服器上的,所以最後報出瞭如下錯誤,意思是在訪問圖片時出現了跨域問題:

image 跨域錯誤
二、問題梳理
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
這段話,用我自己的理解來解釋一下:
- 加了 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,結果圖片是可以載入了,但又得到如下錯誤。

image.png
這段錯誤的意思是,這一個來自於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 中的複用。
最後,感謝你能讀到並讀完此文章,如果分析的過程中存在錯誤或者疑問都歡迎留言討論。如果我的分享能夠幫助到你,還請記得幫忙點個贊吧,謝謝。