在 Canvas 中使用跨域图片

HTML 为图片提供了 crossorigin 属性,结合适当的 CORS 标头,它允许从外部源加载的 <img> 元素定义的图片在 <canvas> 中使用,就像它们是从当前源加载的一样。

有关 crossorigin 属性如何使用的详细信息,请参阅 CORS 设置属性

安全性和被“污染”的 Canvas

由于 Canvas 位图中的像素可能来自各种来源,包括从其他主机检索的图片或视频,因此不可避免地会出现安全问题。

一旦你在 Canvas 中绘制了任何未经 CORS 批准从其他源加载的数据,Canvas 就会被污染。一个被污染的 Canvas 不再被认为是安全的,任何尝试从 Canvas 中检索图片数据的行为都会导致抛出异常。

如果外部内容源是 HTML <img> 或 SVG <svg> 元素,则不允许尝试检索 Canvas 的内容。

如果外部内容来自 HTMLCanvasElementImageBitMap 获取的图片,并且图片源不符合同源策略,则尝试读取 Canvas 内容将被阻止。

对被污染的 Canvas 调用以下任何方法都会导致错误

当 Canvas 被污染时尝试执行这些操作将导致抛出 SecurityError。这可以保护用户,防止通过使用图片从远程网站未经许可地获取信息,从而暴露私人数据。

存储来自外部源的图片

在此示例中,我们希望允许从外部源检索图片并将其保存到本地存储。实现这一点需要配置服务器并为网站本身编写代码。

Web 服务器配置

我们首先需要一个配置为托管图片的服务器,其中 Access-Control-Allow-Origin 标头配置为允许跨域访问图片文件。

假设我们正在使用 Apache 提供我们的网站。请考虑 HTML5 Boilerplate 的 用于 CORS 图片的 Apache 服务器配置文件,如下所示

apacheconf
<IfModule mod_setenvif.c>
  <IfModule mod_headers.c>
    <FilesMatch "\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$">
      SetEnvIf Origin ":" IS_CORS
      Header set Access-Control-Allow-Origin "*" env=IS_CORS
    </FilesMatch>
  </IfModule>
</IfModule>

简而言之,这会将服务器配置为允许图形文件(扩展名为“.bmp”、“.cur”、“.gif”、“.ico”、“.jpg”、“.jpeg”、“.png”、“.svg”、“ .svgz" 和 ".webp")可以从互联网上的任何位置进行跨域访问。

实现保存功能

现在服务器已配置为允许跨域检索图片,我们可以编写代码,允许用户将它们保存到 本地存储,就像它们是从运行代码的同一域提供的一样。

关键是通过将 HTMLImageElement 上的 crossOrigin 设置为 crossorigin 属性来允许图片加载。这会告诉浏览器在下载图片数据时请求跨域访问。

开始下载

开始下载的代码(例如,当用户点击“下载”按钮时)如下所示

js
function startDownload() {
  let imageURL = "https://mdn.github.io/shared-assets/images/examples/mdn.svg";
  let imageDescription = "Logo of a dinosaur in front of a map";

  downloadedImg = new Image();
  downloadedImg.crossOrigin = "anonymous";
  downloadedImg.addEventListener("load", imageReceived);
  downloadedImg.alt = imageDescription;
  downloadedImg.src = imageURL;
}

我们在这里使用硬编码的 URL (imageURL) 和相关的描述性文本 (imageDescription),但这可以很容易地来自任何地方。要开始下载图片,我们使用 Image() 构造函数创建一个新的 HTMLImageElement 对象。然后将图片配置为通过将其 crossOrigin 属性设置为 "anonymous" 来允许跨域下载(即,允许未经身份验证的跨域下载图片)。为图片元素上触发的 load 事件添加了一个事件监听器,这意味着图片数据已收到。为图片添加了替代文本;虽然 <canvas> 不支持 alt 属性,但该值可用于设置 aria-label 或 Canvas 的内部内容。

最后,图片的 src 属性设置为要下载图片的 URL;这会触发下载开始。

接收并保存图片

处理新下载图片的代码在 imageReceived() 方法中

js
function imageReceived() {
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");

  canvas.width = downloadedImg.width;
  canvas.height = downloadedImg.height;
  canvas.innerText = downloadedImg.alt;

  context.drawImage(downloadedImg, 0, 0);
  imageBox.appendChild(canvas);

  try {
    localStorage.setItem("saved-image-example", canvas.toDataURL("image/png"));
  } catch (err) {
    console.error(`Error: ${err}`);
  }
}

imageReceived() 被调用来处理接收下载图片的 HTMLImageElement 上的 "load" 事件。一旦所有下载数据都可用,此事件就会触发。它首先创建一个新的 <canvas> 元素,我们将使用它将图片转换为数据 URL,并通过变量 context 获取 Canvas 的 2D 绘图上下文 (CanvasRenderingContext2D) 的访问权限。

Canvas 的大小调整为与接收到的图片匹配,内部文本设置为图片描述,然后使用 drawImage() 将图片绘制到 Canvas 中。然后将 Canvas 插入文档中,以便图片可见。

现在是时候实际在本地保存图片了。为此,我们使用 Web Storage API 的本地存储机制,该机制通过 localStorage 全局访问。Canvas 方法 toDataURL() 用于将图片转换为表示 PNG 图片的 data:// URL,然后使用 setItem() 将其保存到本地存储中。

另见