允许跨域使用图像和画布

HTML 提供了一个用于图像的 crossorigin 属性,该属性与相应的 CORS 标头结合使用,允许由 <img> 元素定义的来自外部来源的图像在 <canvas> 中使用,就好像它们是从当前来源加载的一样。

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

安全和污染的画布

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

一旦您在画布中绘制任何未经 CORS 批准且从其他来源加载的数据,该画布就会变成污染的。污染的画布是指不再被认为安全的画布,任何尝试从画布中检索图像数据的操作都会导致抛出异常。

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

如果外部内容来自从 HTMLCanvasElementImageBitMap 获取的图像,并且图像源不符合同源策略,则会阻止尝试读取画布的内容。

在污染的画布上调用以下任何一项都会导致错误

当画布被污染时尝试执行任何这些操作都会导致抛出 SecurityError。这可以保护用户免受通过使用图像在未经许可的情况下从远程网站提取信息的私有数据泄露。

存储来自外部来源的图像

在此示例中,我们希望允许检索来自外部来源的图像并将其保存到本地存储中。实现此目的需要配置服务器以及为网站本身编写代码。

Web 服务器配置

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

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

xml
<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”扩展名的文件)。

实现保存功能

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

关键是使用 crossorigin 属性,通过设置 crossOrigin 在将加载图像的 HTMLImageElement 中。这告诉浏览器在下载图像数据时请求跨域访问。

开始下载

启动下载的代码(例如,当用户单击“下载”按钮时)如下所示

js
function startDownload() {
  let imageURL =
    "https://cdn.glitch.com/4c9ebeb9-8b9a-4adc-ad0a-238d9ae00bb5%2Fmdn_logo-only_color.svg?1535749917189";
  let imageDescription = "The Mozilla logo";

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

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

最后,将图像的 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 获取画布的 2D 绘图上下文 (CanvasRenderingContext2D)。

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

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

另请参阅