圖片跨域規律探尋

孤舟蓑翁發表於2021-05-20

先說結論:

  1. canvas.toDataURL API中用到的圖片,必須新增crossOrigin屬性設定,否則會報被汙染的canvas無法被匯出的錯誤

  2. url相同,crossOrigin屬性的圖,在頁面中通過html img標籤和js-dom Image物件不管載入多少次,瀏覽器只請求伺服器一次。從快取中讀取時,多次載入也只讀取一次。

  3. 頁面載入多幅url相同的圖片,如果這些圖片中有些設定了跨域屬性,有的未跨域屬性,只要設定了跨域屬性的圖之後會載入沒有跨域屬性的圖,那麼最後快取的就是沒有跨域屬性的圖。

  4. 如果快取的是沒有跨域屬性的圖片,設定了跨域屬性的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>

 

相關文章