图片跨域问题

493 阅读3分钟

1. 图片也需要跨域

说到图片跨域,大部分人的第一反应一般是从来没有遇见过图片跨域问题,这是实话,绝大多数开发处理跨域问题处理的都是 Fetch/XHR 的跨域问题,很少涉及其他资源的跨域,但这并不意味着其他资源没有跨域问题;

浏览器对于跨域资源的范围界定,可以点开控制台-网络,下面所有的资源包括 Fetch/XHR 和 图片、视频、字体之类的都需要解决跨域问题,再加个不在里面显示的 cookie 跨域问题;

一般图片资源的使用是通过 img 标签做到解决跨域,原生 html 标签可以解决跨域问题(经典的 JSONP 解决跨域问题的思路就是这么来的),这也就是为什么很多开发都没遇见过图片跨域问题;

但是这种方式仅限展示资源,跨域仍然限制 JS 读取和操作,在浏览器眼中 加载 ≠ 操作;要特别说明的是,什么叫 JS 读取和操作,浏览器对于不同资源有一套独特的理解,在图片资源中,浏览器认为的 JS 读取和操作就是对图片像素的操作

2. 图片的跨域常见解决方案

最常见的图片解决方案就是 crossOrigin + CDN + CORS

crossOriginHTMLImageElement 上的一个属性,当设置 crossOrigin = 'anonymous' 时,浏览器会将当前页面的 Origin 作为请求头中的 Origin 字段发送给服务器;

然后 CDN 上可以配置 CORS,之后的逻辑和 Fetch/XHR 的跨域问题相似

<img src="https://other.com/image.jpg" crossorigin="anonymous">
<canvas id="canvas"></canvas>

<script>
  const img = document.querySelector('img');
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');

  img.onload = () => {
    ctx.drawImage(img, 0, 0);
    const data = ctx.getImageData(0, 0, img.width, img.height);
    console.log('像素数据:', data);
  };
</script>

HTMLImageElementHTMLCanvasElement 是很经典的图片跨域跨域案例,浏览器会认为绘制 canvas 的过程属于 JS 读取和操作

const image = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
image.crossOrigin = 'anonymous';
image.onload = async () => {
    ctx.drawImage(img, 0, 0);
    const data = ctx.getImageData(0, 0, img.width, img.height);
    console.log('像素数据:', data);
};

也可以用 Image 来实现,本质是一样的

如果以上代码没有设置 CrossOrigin = 'anonymous',那么会报错

SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D':
The canvas has been tainted by cross-origin data.

3. 跨域/CORS的实质

大多数人都背过跨域/CORS的面经题,这里简单概括为浏览器自动带上 Origin 请求标头,然后服务器添加上 Access-Control-* 的响应标头,当浏览器接收到请求后进行校验;

但是这里有个问题,并没什么规定服务端返回的响应标头是什么,理论上服务端想返回什么就返回什么,甚至可以在没有带上 Origin 请求标头的情况下返回 Access-Control-* 的响应标头;

这种情况在 CDN 中颇为常见,很多 CDN 都会设置 Access-Control-Allow-Origin = *,多数人也不会修改这些配置,这意味着很多图片资源的请求跨域校验在 CDN 服务端上一直是通过的,那为什么还会需要 CrossOrigin 呢?

因为 CrossOrigin 的主要意义是让浏览器添加 Origin,而跨域通过需要浏览器添加+服务器返回,两者不可缺失,即使服务端允许所有人跨域通过浏览器还是会要求通过完整跨域流程才能跨域;

4. 另一种图片跨域解决思路:Fetch

Fetch 解决跨域也是通过 CORS 做到的,只是大部分项目都应该有相关配置,所以图片跨域问题可以用 Fetch 解决;

fetch(imageUrl)
  .then(res => res.arrayBuffer())
  .then(buffer => {
    const blob = new Blob([buffer]);
    const url = URL.createObjectURL(blob);
    const img = new Image();
    img.src = url;
    // img.crossOrigin = 'anonymous';
    img.onload = () => {
        // 操作代码同上
    };
});

这个思路获取了 ArrayBuffer 后转成 URL,这是一个以 blob: 开头的临时本地 URL,它指向浏览器内存中的一块数据(即创建的 Blob 对象),而不是网络地址,不需要处理跨域;

这个思路明显比 CrossOrigin 复杂,不会用于一般的图片资源,往往用于特殊资源而且会在获取 buffer 后做特别处理