先說結論:
-
canvas.toDataURL API中用到的圖片,必須添加crossOrigin屬性設置,否則會報被污染的canvas無法被導出的錯誤
-
url相同,crossOrigin屬性的圖,在頁面中通過html img標簽和js-dom Image對象不管加載多少次,瀏覽器只請求服務器一次。從緩存中讀取時,多次加載也只讀取一次。
-
頁面加載多幅url相同的圖片,如果這些圖片中有些設置了跨域屬性,有的未跨域屬性,只要設置了跨域屬性的圖之后會加載沒有跨域屬性的圖,那么最后緩存的就是沒有跨域屬性的圖。
-
如果緩存的是沒有跨域屬性的圖片,設置了跨域屬性的html img標簽,js-dom Image對象從緩存中加載圖片,會報跨域錯誤。如果緩存的是設置了跨域屬性的圖片,html img標簽,js-dom Image對象 無論是否設置跨域屬性,都可以從緩存中正常加載圖片。
再看實驗過程:
1.分別加載沒有設置crossOrigin屬性的html-img和js-img圖片,調用canvas.toDataURL轉換data URI,執行時都會報錯-被污染的canvas無法被導出,這個錯誤是由於canvas使用了未設置跨域的圖片資源引起的,只有設置了crossOrigin屬性的圖片資源,才能被canvas復用。
1.1 加載沒有設置crossOrigin="anonymous"屬性的html-img圖片,執行canvas.toDataURL
<img alt="" id="html-img" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <div id="js-canvas-box"></div> <script> // 將圖片繪制在canvas畫布上 function convertCanvasToImage(image) { const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); // 從canvas畫布導出圖片 const img = new Image(); img.src = canvas.toDataURL('image/png'); return img; } // 通過html-img標簽加載圖片 const htmlImg = document.querySelector('#html-img'); htmlImg.onload = function () { const img=convertCanvasToImage(this); document.querySelector('#js-canvas-box').appendChild(img); }; </script>
報如下錯誤:
1.2 加載沒有設置crossOrigin="anonymous"屬性的js-image圖片,執行canvas.toDataURL
<div id="js-canvas-box" /> <script> // 將圖片繪制在canvas畫布上 function convertCanvasToImage(image) { const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); // 從canvas畫布導出圖片 const img = new Image(); img.src = canvas.toDataURL('image/png'); return img; } // 通過js-dom加載圖片 const jsImg = new Image(); jsImg.src ='https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png'; jsImg.onload = function () { const img=convertCanvasToImage(this); document.querySelector('#js-canvas-box').appendChild(img); }; </script>
報如下錯誤:
2.分別加載設置crossOrigin屬性時html-img和js-img圖片,調用canvas.toDataURL執行都正常。
crossOrigin
可以有下面兩個值:
anonymous | 元素的跨域資源請求不需要憑證標志設置。 |
use-credentials | 元素的跨域資源請求需要憑證標志設置,意味着該請求需要提供憑證 |
只要crossOrigin的屬性值不是use-credentials,全部都會解析為anonymous,包括空字符串。
2.1 加載設置crossOrigin屬性時html-img圖片,執行canvas.toDataURL,結果正確。
<img alt="" id="html-img" crossOrigin="" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <div id="js-canvas-box" /> <script> // 將圖片繪制在canvas畫布上 function convertCanvasToImage(image) { const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); // 從canvas畫布導出圖片 const img = new Image(); img.src = canvas.toDataURL('image/png'); return img; } // 通過html-img標簽加載圖片 const htmlImg = document.querySelector('#html-img'); htmlImg.onload = function () { const img=convertCanvasToImage(this); document.querySelector('#js-canvas-box').appendChild(img); }; </script>
2.2 加載設置crossOrigin屬性時js-img圖片,執行canvas.toDataURL,結果正確。
<div id="js-canvas-box" /> <script> function convertCanvasToImage(image) { // 將圖片繪制在canvas畫布上 const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); // 從canvas畫布導出圖片 const img = new Image(); img.src = canvas.toDataURL('image/jpg'); return img } // 通過js-dom加載圖片 const jsImg = new Image(); // 只要crossOrigin的屬性值不是use-credentials,全部都會解析為anonymous,包括空字符串。 jsImg.crossOrigin="" jsImg.src ='https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png'; jsImg.onload = function () { const image=convertCanvasToImage(jsImg); document.querySelector('#js-canvas-box').appendChild(image); }; </script>
3.再看看從緩存加載,會不會報錯。啟用緩存,分別加載設置crossOrigin屬性時html-img和js-img圖片,執行canvas.toDataURL,也都沒有報錯。
3.1 啟用緩存,加載設置crossOrigin屬性時html-img圖片,執行canvas.toDataURL,結果正確。
<img alt="" id="html-img" crossOrigin="anonymous" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <div id="js-canvas-box" /> <script> function convertCanvasToImage(image) { // 將圖片繪制在canvas畫布上 const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); // 從canvas畫布導出圖片 const img = new Image(); img.src = canvas.toDataURL('image/jpg'); return img } // 通過html-img加載圖片 const htmlImg = document.querySelector('#html-img'); htmlImg.onload = function () { const image=convertCanvasToImage(this); document.querySelector('#js-canvas-box').appendChild(image); }; </script>
3.2 啟用緩存,加載設置crossOrigin屬性時js-img圖片,執行canvas.toDataURL,結果正確。
<div id="js-canvas-box" /> <script> function convertCanvasToImage(image) { // 將圖片繪制在canvas畫布上 const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); // 從canvas畫布導出圖片 const img = new Image(); img.src = canvas.toDataURL('image/jpg'); return img } // 通過js-dom加載圖片 const jsImg = new Image(); // 只要crossOrigin的屬性值不是use-credentials,全部都會解析為anonymous,包括空字符串。 jsImg.crossOrigin="" jsImg.src ='https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png'; jsImg.onload = function () { const image=convertCanvasToImage(this); document.querySelector('#js-canvas-box').appendChild(image); }; </script>
4. 通過html-img和js-img加載url相同,crossOrigin屬性的圖,只加載一次。從緩存中讀取時,也只讀取一次
4.1 html-img和js-img都未設置crossOrigin,加載同一幅圖,只加載一次。
<img alt="img" id="html-img" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <div id="js-canvas-box"></div> <script> const jsImg = new Image(); jsImg.src ='https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png'; jsImg.onload = function () { document.querySelector('#js-canvas-box').appendChild(jsImg); }; </script>
從緩存中讀取,只讀取了一次。
4.2 html-img和js-img都設置crossOrigin,加載同一幅圖,只加載一次。
<img alt="img" id="html-img" crossOrigin="" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <div id="js-canvas-box"></div> <script> const jsImg = new Image(); jsImg.crossOrigin="" jsImg.src ='https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png'; jsImg.onload = function () { document.querySelector('#js-canvas-box').appendChild(this); }; </script>
從緩存中讀取,只讀取一次。
5. 再看看通過html-img的方式加載url相同,crossOrigin屬性不同的情景,頁面加載多個url一樣的html-img圖片,如果在跨域屬性圖片之后加載了沒有跨域屬性的圖片,那么最后緩存的是未設置crossOrigin屬性的圖片,刷新頁面,那些設置了crossOrigin屬性的圖片,從緩存中加載圖片時,會報跨域錯誤
這是因為:
- 在頁面加載的過程中,圖片會被瀏覽器緩存,如果再次遇到url和crossOrigin屬性相同的圖片,直接會從緩存中讀取,如果url相同,crossOrigin屬性與之前緩存的圖片不同,瀏覽器會重新請求,並重新緩存,覆蓋之前緩存的同一張圖。可是緩存中的圖片跨域屬性一旦從跨域變成不跨域,之后瀏覽器便不會在覆蓋之前的緩存。緩存的圖片始終保持為不跨域。
- 緩存的圖片如果是未設置跨域屬性的圖片,html-img標簽設置了crossOrigin屬性,從緩存加載,會觸發跨域問題。緩存的圖片如果是設置了跨域屬性的圖片,無論html-img標簽是否設置crossOrigin屬性,從緩存加載,都不會觸發跨域問題。
5.1 最后緩存的是沒有設置crossOrigin屬性的圖片, 從緩存中加載時,觸發了html img標簽中設置了crossOrigin屬性圖片的跨域。
<!-- 緩存的是沒有跨域屬性的圖片 --> <img alt="img-3-no" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <!-- 緩存被覆蓋,緩存的是有跨域屬性的圖片 --> <img alt="img-2-anonymous" crossOrigin="anonymous" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <!-- 緩存再次被覆蓋,緩存的是沒有跨域屬性的圖片 --> <img alt="img-3-no" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <!-- 緩存中同一幅圖的跨域屬性一旦由跨域變成不跨域,之后瀏覽器不會再修改圖片的跨域屬性 --> <img alt="img-4-anonymous" crossOrigin="anonymous" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" />
1.5kb的圖片是沒有跨域屬性的圖片,1.6kb的圖片是設置了跨域屬性的圖片,從網絡請求面板可以看到,最后請求的是沒有跨域屬性的圖片,意味着最后緩存的也是沒有跨域屬性的圖片。
設置了跨域屬性的html img標簽,從緩存中加載沒有跨域屬性的圖片,瀏覽器會報跨域錯誤。
5.2 最后緩存的圖是設置了crossOrigin的圖片,從緩存中加載時, 不會觸發html img標簽中未設置crossOrigin屬性圖片的跨域。
<!-- 緩存的是沒有跨域屬性的圖片 --> <img alt="img-3-no" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <!-- 緩存過,不再緩存 --> <img alt="img-2-no" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <!-- 緩存過,不再緩存 --> <img alt="img-3-no" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <!-- 緩存被覆蓋,緩存的是有跨域屬性的圖片 --> <img alt="img-4-anonymous" crossOrigin="anonymous" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" />
從網絡請求面板可以看出,最后請求的是1.6kb的設置了跨域屬性的圖片,意味着緩存中最后保持的也是有跨域屬性的圖片
html img標簽即使未設置跨域屬性,也能利用緩存中保存的設置了跨域屬性的圖片,不會報錯。
6.再看看html-img和js-img混搭加載的情景, 頁面加載多個url相同的html-img和js-img混搭圖片,前面的結論依舊成立。
6.1 最后緩存的是沒有設置crossOrigin屬性的圖片,從緩存加載時,設置了crossOrigin屬性的圖片會報跨域錯誤。
<img alt="img" id="html-img" crossOrigin="anonymous" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <div id="js-canvas-box"></div> <script> const jsImg = new Image(); jsImg.src ='https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png'; jsImg.onload = function () { document.querySelector('#js-canvas-box').appendChild(jsImg); }; </script>
6.2 最后緩存的是設置了crossOrigin屬性的圖片,從緩存加載時,不會觸發沒有設置crossOrigin屬性的圖片跨域錯誤。
<img alt="img" id="html-img" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png" /> <div id="js-canvas-box"></div> <script> const jsImg = new Image(); jsImg.crossOrigin="anonymous" jsImg.src ='https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png'; jsImg.onload = function () { document.querySelector('#js-canvas-box').appendChild(jsImg); }; </script>