选择性格式读取:Async Clipboard API 的更佳默认设置

Prashant Singh
Prashant Singh

发布时间:2026 年 7 月 3 日

如果您曾使用过文档编辑器、电子表格应用或任何将从剪贴板粘贴内容作为主要互动的应用,您就会知道用户的期望:粘贴操作应立即完成。

遗憾的是,在许多情况下,Web 应用读取的剪贴板数据量超过了所需量,这可能会导致粘贴内容的速度比应有的速度慢,浪费 CPU 周期,并使用内存来存储应用永远不需要的数据。

选择性格式读取通过修改 Async Clipboard API 来更好地满足用户期望并提供开发者所需的功能,即仅读取应用实际需要的格式,从而解决此性能问题。

问题:预先读取所有数据格式

当用户从任何应用(例如 Excel、Google 文档或 Photoshop)中复制内容时,该应用通常会将同一数据的多种表示形式写入系统剪贴板:纯文本、HTML、图片,有时还会写入自定义格式。读取数据的 Web 应用(当用户从剪贴板粘贴数据时)很少需要所有这些表示形式。电子表格编辑者可能需要 HTML。文本编辑器可能需要公开“粘贴为纯文本”命令。代码编辑器可能只需要文本,而不需要其他任何内容。

在此之前,navigator.clipboard.read() 会从系统剪贴板中提取数据的每种可用表示形式,运行清理(这可能需要时间,尤其是在 HTML 或图片载荷较大时),并将字节保留在内存中,即使对于应用永远不会使用的格式也是如此。未使用的载荷越大,用户需要等待的时间就越长,标签页消耗的内存也就越多,而粘贴响应速度在此时最为重要。

解决方案:仅枚举,然后按需读取

从 Chrome 和 Edge 149 开始,navigator.clipboard.read() 方法返回的 ClipboardItem 对象仅描述剪贴板中的内容,而不是立即提供对数据的访问权限。只有在您调用 ClipboardItem.getType(mimeType) 时,才会从剪贴板读取给定格式的实际字节。API Surface 保持不变。您昨天编写的相同代码今天仍然有效,可立即带来性能优势:

// Enumerate the available MIME types.
// No payload is fetched yet.
const items = await navigator.clipboard.read();

for (const item of items) {
  if (item.types.includes('text/html')) {
    // Only read the HTML data.
    // The data is fetched (and sanitized) here only.
     const blob = await item.getType('text/html');
     useThePastedHtml(await blob.text());
     break;
   }

   // text/plain, image/png, or any other custom formats
   // stored in the clipboard are never read.
}

另一个性能优势是缓存是自动进行的。对同一 ClipboardItem 调用 getType('text/html') 两次时,系统只会从剪贴板读取一次,并在第二次调用时返回缓存的 Blob

优势

延迟时间。当调用 navigator.clipboard.read() 时需要完成的工作(例如跨进程复制字节、清理 HTML 或解码图片)现在仅针对应用实际需要的格式执行。仅读取单一格式的应用可节省大部分内存。跳过的格式越大,增幅就越大。HTML 受益最大,因为 HTML 清理成本很高。

内存。以前,当剪贴板中包含 50 MB 的图片时,调用 navigator.clipboard.read() 意味着需要在网页的渲染器内存中使用 50 MB 的空间,直到 ClipboardItem 被释放为止,即使应用只需要数据的 text/plain 表示形式也是如此。现在,只有应用在调用 ClipboardItem.getType() 时读取的格式才会使用渲染器进程中的内存。对于经常需要粘贴的应用(例如文档编辑器、邮件撰写器或设计工具),此功能可显著减少粘贴期间的内存用量,尤其是在多标签页会话中,多个编辑器可能会并行读取剪贴板。

无需选择启用。网络上的每个网站都会自动获得这些好处。您无需迁移代码或将代码置于功能检测逻辑之后。

需要注意的一项行为变更

稍后读取数据时,行为会略有变化:ClipboardItem 对象不再是调用 navigator.clipboard.read() 时剪贴板的冻结快照。

read() 被调用并返回 ClipboardItem 时,此项只是系统剪贴板中数据的延迟句柄。如果剪贴板的内容在调用 read() 和稍后调用 getType() 之间发生变化,则该调用将被拒绝,并返回 InvalidStateError。即使对于您之前在同一 ClipboardItem 上成功读取的格式,也是如此。

// Read from the clipboard.
const items = await navigator.clipboard.read();
// Get the first ClipboardItem.
const item = items[0];

// Actually fetch the data now, which works fine.
const text = await item.getType('text/plain');

// The user writes something new to the clipboard
// by copying from another app, or your own code writes to
// the clipboard again.
await navigator.clipboard.writeText('something new');

// Fetching the data from the earlier ClipboardItem
// rejects with InvalidStateError, because the contents
// of the clipboard changed.
const html = await item.getType('text/html');

这是因为返回不再反映剪贴板的数据会造成混淆,否则会导致难以调试的问题。

实际上,大多数代码都看不到此更改,因为应用通常会在同一粘贴处理程序中连续调用 read()getType()。不过,您可能需要留意代码在异步边界上持有 ClipboardItem 的情况。

以下示例展示了此项更改可能会造成的问题:在 window 上缓存 ClipboardItem 对象、在剪贴板历史记录界面中显示这些对象,或在调用 getType() 之前等待用户互动。为避免出现此类情形的问题,请执行以下操作:

  • getType() 调用封装在 try / catch 块中,并将 InvalidStateError 视为剪贴板已移动的信号,代码应再次读取。
  • 如果您需要保留剪贴板数据,请在getType()后立即将 Blob 复制到您自己的结构中。请勿将 ClipboardItem 视为长期存储空间。
  • 使用 clipboardChange 事件主动使过时的句柄失效。

标准和浏览器支持

选择性格式读取是 Web 编辑工作组和 Microsoft Edge Web 平台团队推动的 Clipboard API 规范变更

Safari 也支持此行为,而 Firefox 已表明其积极的标准立场,并打算保持一致。

对剪贴板 API 进行了一项不显眼但意义重大的性能和互操作性改进,Chromium 现在正向同一模型收敛。