在 Canvas 中使用跨域图片
HTML 为图片提供了 crossorigin
属性,结合适当的 CORS 标头,它允许从外部源加载的 <img>
元素定义的图片在 <canvas>
中使用,就像它们是从当前源加载的一样。
有关 crossorigin
属性如何使用的详细信息,请参阅 CORS 设置属性。
安全性和被“污染”的 Canvas
由于 Canvas 位图中的像素可能来自各种来源,包括从其他主机检索的图片或视频,因此不可避免地会出现安全问题。
一旦你在 Canvas 中绘制了任何未经 CORS 批准从其他源加载的数据,Canvas 就会被污染。一个被污染的 Canvas 不再被认为是安全的,任何尝试从 Canvas 中检索图片数据的行为都会导致抛出异常。
如果外部内容源是 HTML <img>
或 SVG <svg>
元素,则不允许尝试检索 Canvas 的内容。
如果外部内容来自 HTMLCanvasElement
或 ImageBitMap
获取的图片,并且图片源不符合同源策略,则尝试读取 Canvas 内容将被阻止。
对被污染的 Canvas 调用以下任何方法都会导致错误
- 对 Canvas 的上下文调用
getImageData()
- 对
<canvas>
元素本身调用toBlob()
、toDataURL()
或captureStream()
当 Canvas 被污染时尝试执行这些操作将导致抛出 SecurityError
。这可以保护用户,防止通过使用图片从远程网站未经许可地获取信息,从而暴露私人数据。
存储来自外部源的图片
在此示例中,我们希望允许从外部源检索图片并将其保存到本地存储。实现这一点需要配置服务器并为网站本身编写代码。
Web 服务器配置
我们首先需要一个配置为托管图片的服务器,其中 Access-Control-Allow-Origin
标头配置为允许跨域访问图片文件。
假设我们正在使用 Apache 提供我们的网站。请考虑 HTML5 Boilerplate 的 用于 CORS 图片的 Apache 服务器配置文件,如下所示
<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
属性来允许图片加载。这会告诉浏览器在下载图片数据时请求跨域访问。
开始下载
开始下载的代码(例如,当用户点击“下载”按钮时)如下所示
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()
方法中
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()
将其保存到本地存储中。