图片跨域规律探寻
先说结论:
-
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>