图片跨域规律探寻

先说结论:

  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="//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 ='//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="//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 ='//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="//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 ='//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="//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 ='//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="//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 ='//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="//sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png"
/>

<!-- 缓存被覆盖,缓存的是有跨域属性的图片 -->
<img
alt="img-2-anonymous"
crossOrigin="anonymous"
src="//sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png"
/>


<!-- 缓存再次被覆盖,缓存的是没有跨域属性的图片 -->
<img
alt="img-3-no"
src="//sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png"
/>
<!-- 缓存中同一幅图的跨域属性一旦由跨域变成不跨域,之后浏览器不会再修改图片的跨域属性 -->
<img
alt="img-4-anonymous"
crossOrigin="anonymous"

src="//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="//sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png"
/>

<!-- 缓存过,不再缓存 -->
<img
alt="img-2-no"
src="//sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png"
/>


<!-- 缓存过,不再缓存 -->
<img
alt="img-3-no"
src="//sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png"
/>
<!-- 缓存被覆盖,缓存的是有跨域属性的图片 -->
<img
alt="img-4-anonymous"
crossOrigin="anonymous"

src="//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="//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 ='//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="//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 ='//sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/app-install.81ece51.png';
  jsImg.onload = function () {
    document.querySelector('#js-canvas-box').appendChild(jsImg);
  };
</script>