取消屏蔽剪贴板访问权限

更安全地访问剪贴板中的文字和图片

访问系统剪贴板的传统方式是 document.execCommand() 进行剪贴板互动虽然这种剪辑方式广受广泛支持, 粘贴需要付费:剪贴板访问是同步的,并且只能读取 并写入 DOM。

这很适合一小段文字,但在很多情况下 页面会给用户带来很糟糕的体验。在内容可以安全地粘贴之前,可能需要进行耗时的清理或图片解码。浏览器可能需要从粘贴的文件中加载或内嵌关联的资源。那就是 在等待磁盘或网络时阻止相应网页。想象添加权限 也要求浏览器在请求网页的同时 剪贴板访问权限。与此同时,为 document.execCommand() 设置的剪贴板互动权限定义较为宽泛,因浏览器而异。

通过 异步剪贴板 API 并提供了明确的权限模型, 屏蔽该网页Async Clipboard API 仅限于在大多数浏览器上处理文本和图片,但支持情况因浏览器而异。请务必仔细研究以下各部分的浏览器兼容性概览。

复制:将数据写入剪贴板

writeText()

如需将文本复制到剪贴板,请调用 writeText()。由于此 API 是 异步,writeText() 函数会返回一个 Promise,用于解析或 则根据传递的文本是否成功复制来拒绝:

async function copyPageUrl() {
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('Page URL copied to clipboard');
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}

浏览器支持

  • Chrome:66.
  • Edge:79。
  • Firefox:63。
  • Safari:13.1.

来源

write()

实际上,writeText() 只是通用 write() 方法的便捷方法,您还可以使用该方法将图片复制到剪贴板。与 writeText() 一样,它是异步的,并会返回一个 Promise。

如需将图片写入剪贴板,您需要将图片作为 blob 进行处理。方法之一 方法是使用 fetch() 从服务器请求图片,然后调用 blob()(位于 响应。

从服务器请求图像对于 造成干扰。幸运的是,您还可以在画布上绘制图片 调用画布 toBlob() 方法。

接下来,将 ClipboardItem 对象的数组作为参数传递给 write() 方法。目前,您一次只能传递一张图片,但我们希望将来能够支持多张图片。ClipboardItem 接受一个对象,其中图片的 MIME 类型用作键,blob 用作值。对于从 fetch()canvas.toBlob() 获取的 blob 对象,blob.type 属性会自动包含图片的正确 MIME 类型。

try {
  const imgURL = '/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem({
      // The key is determined dynamically based on the blob's type.
      [blob.type]: blob
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

或者,您也可以将 promise 写入 ClipboardItem 对象。 对于此模式,您需要事先知道数据的 MIME 类型。

try {
  const imgURL = '/images/generic/file.png';
  await navigator.clipboard.write([
    new ClipboardItem({
      // Set the key beforehand and write a promise as the value.
      'image/png': fetch(imgURL).then(response => response.blob()),
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

浏览器支持

  • Chrome:76。
  • Edge:79。
  • Firefox:127。
  • Safari:13.1。

来源

复制事件

在用户发起剪贴板复制操作的情况下 并且不会调用 preventDefault(),即 copy 个事件 包含 clipboardData 属性,其中各项内容的格式已经正确。 如果您想实现自己的逻辑,则需要调用 preventDefault() 来 以防止出现有利于您自己的实现的默认行为。 在这种情况下,clipboardData 将为空。假设某个网页包含文本和图片,当用户全选并发起剪贴板复制操作时,您的自定义解决方案应舍弃文本,仅复制图片。您可以按照以下代码示例所示来实现此目的。本示例未介绍如何在不支持 Clipboard API 时回退到较早的 API。

<!-- The image we want on the clipboard. -->
<img src="kitten.webp" alt="Cute kitten.">
<!-- Some text we're not interested in. -->
<p>Lorem ipsum</p>
document.addEventListener("copy", async (e) => {
  // Prevent the default behavior.
  e.preventDefault();
  try {
    // Prepare an array for the clipboard items.
    let clipboardItems = [];
    // Assume `blob` is the blob representation of `kitten.webp`.
    clipboardItems.push(
      new ClipboardItem({
        [blob.type]: blob,
      })
    );
    await navigator.clipboard.write(clipboardItems);
    console.log("Image copied, text ignored.");
  } catch (err) {
    console.error(err.name, err.message);
  }
});

对于 copy 事件:

浏览器支持

  • Chrome:1.
  • Edge:12.
  • Firefox:22.
  • Safari:3.

来源

对于 ClipboardItem

浏览器支持

  • Chrome:76.
  • Edge:79。
  • Firefox:127。
  • Safari:13.1.

来源

粘贴:从剪贴板读取数据

readText()

如需从剪贴板读取文本,请调用 navigator.clipboard.readText() 并等待返回的 promise 解析:

async function getClipboardContents() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted content: ', text);
  } catch (err) {
    console.error('Failed to read clipboard contents: ', err);
  }
}

浏览器支持

  • Chrome:66。
  • Edge:79。
  • Firefox:125.
  • Safari:13.1.

来源

read()

navigator.clipboard.read() 方法也是异步的,并会返回一个 Promise。如需读取剪贴板中的图片,请获取 ClipboardItem 然后对其进行迭代。

每个 ClipboardItem 都可以以不同的类型存储其内容,因此您需要再次使用 for...of 循环来迭代类型列表。对于每种类型 使用当前类型作为参数调用 getType() 方法,以获取 相应的 Blob。和之前一样,此代码不与图片绑定, 支持日后上传的其他文件类型

async function getClipboardContents() {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        const blob = await clipboardItem.getType(type);
        console.log(URL.createObjectURL(blob));
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
}

浏览器支持

  • Chrome:76。
  • Edge:79。
  • Firefox:127.
  • Safari:13.1.

来源

处理粘贴的文件

让用户能够使用剪贴板键盘快捷键(例如 Ctrl+CCtrl+V)非常有用。Chromium 会在剪贴板上公开只读文件,如下所述。当用户按操作系统的默认粘贴快捷键,或者在浏览器的菜单栏中依次点击编辑粘贴时,此操作会触发。无需其他管道代码。

document.addEventListener("paste", async e => {
  e.preventDefault();
  if (!e.clipboardData.files.length) {
    return;
  }
  const file = e.clipboardData.files[0];
  // Read the file's contents, assuming it's a text file.
  // There is no way to write back to it.
  console.log(await file.text());
});

浏览器支持

  • Chrome:3.
  • Edge:12.
  • Firefox:3.6。
  • Safari:4.

来源

粘贴事件

如前所述,我们计划推出可与 Clipboard API 搭配使用的事件,但目前您可以使用现有的 paste 事件。它与用于读取剪贴板文本的新异步方法配合使用效果非常出色。与 copy 事件一样,切勿 忘记调用 preventDefault()

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const text = await navigator.clipboard.readText();
  console.log('Pasted text: ', text);
});

浏览器支持

  • Chrome:1.
  • Edge:12。
  • Firefox:22.
  • Safari:3.

来源

处理多个 MIME 类型

大多数实现会将多种数据格式放到剪贴板中,以进行一次剪切 或复制操作。这样做有两个原因:作为应用开发者, 无法了解用户想要将文字或图片复制到哪些应用的功能, 许多应用都支持以纯文本形式粘贴结构化数据。这通常是 修改菜单项(名称类似于粘贴 匹配样式或粘贴(不带格式)

以下示例展示了如何执行此操作。此示例使用 fetch() 获取 但它也可能来自 <canvas>File System Access API

async function copy() {
  const image = await fetch('kitten.png').then(response => response.blob());
  const text = new Blob(['Cute sleeping kitten'], {type: 'text/plain'});
  const item = new ClipboardItem({
    'text/plain': text,
    'image/png': image
  });
  await navigator.clipboard.write([item]);
}

安全与权限

对浏览器而言,剪贴板访问一直是安全隐患。不包含 适当的权限,网页可以暗中复制各种恶意内容 粘贴到用户的剪贴板中;粘贴时会产生灾难性的结果。 假设某个网页会静默复制 rm -rf /解压缩炸弹图片 复制到剪贴板

浏览器提示,要求用户授予剪贴板权限。
Clipboard API 的权限提示。

授予网页对剪贴板的无限读取权限更是锦上添花 很麻烦。用户会定期复制敏感信息,例如密码和 将个人详细信息复制到剪贴板 用户的知识。

与许多新 API 一样,只有通过 HTTPS 传输的页面才支持 Clipboard API。为防止滥用,只有当网页是处于活动状态的标签页时,才允许访问剪贴板。使用中的标签页中的页面可以在不向剪贴板中写入内容的情况下 请求权限,但从剪贴板读取内容始终需要 权限。

Permissions API。 当页面处于活动标签页状态时,系统会自动向其授予 clipboard-write 权限。必须请求 clipboard-read 权限,才能 来做到这一点以下代码展示了后者:

const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

// Listen for changes to the permission state
permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};

您还可以控制是否需要用户手势才能调用剪切或 使用 allowWithoutGesture 选项进行粘贴。该值的默认值 因浏览器而异,所以一定要在列表中加入。

这时,Clipboard API 的异步特性非常有用:如果用户尚未授予权限,那么尝试读取或写入剪贴板数据时,系统会自动提示用户授予权限。由于该 API 基于 Promise,因此这一点完全透明,如果用户拒绝授予剪贴板权限,系统会导致 Promise 被拒绝,以便网页能够做出适当响应。

由于浏览器仅在网页是活动标签页时才允许访问剪贴板,因此您会发现,如果直接将以下示例粘贴到浏览器的控制台中,这些示例将无法运行,因为开发者工具本身就是活动标签页。这里有一个技巧:使用 setTimeout() 延迟剪贴板访问,然后在调用函数之前快速点击页面以将焦点移至该页面:

setTimeout(async () => {
  const text = await navigator.clipboard.readText();
  console.log(text);
}, 2000);

权限政策集成

要在 iframe 中使用该 API,您需要通过以下方式启用它: “权限”政策、 它定义了一种机制,允许有选择性地启用 停用各种浏览器功能和 API。具体而言,您需要传递 clipboard-read 和/或 clipboard-write,具体取决于应用的需求。

<iframe
    src="index.html"
    allow="clipboard-read; clipboard-write"
>
</iframe>

功能检测

如需使用 Async Clipboard API 并支持所有浏览器,请测试 navigator.clipboard 并回退到早期方法。例如,您可以通过以下方式实现粘贴功能,以添加其他浏览器。

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  let text;
  if (navigator.clipboard) {
    text = await navigator.clipboard.readText();
  }
  else {
    text = e.clipboardData.getData('text/plain');
  }
  console.log('Got pasted text: ', text);
});

这还不是全部。在 Async Clipboard API 出现之前, 在不同网络浏览器间实现不同的复制和粘贴操作。在大多数浏览器中,可以使用 document.execCommand('copy')document.execCommand('paste') 触发浏览器自己的复制和粘贴功能。如果要复制的文本是 DOM 中不存在的字符串,则必须将其注入 DOM 并选择:

button.addEventListener('click', (e) => {
  const input = document.createElement('input');
  input.style.display = 'none';
  document.body.appendChild(input);
  input.value = text;
  input.focus();
  input.select();
  const result = document.execCommand('copy');
  if (result === 'unsuccessful') {
    console.error('Failed to copy text.');
  }
  input.remove();
});

演示

您可以使用以下演示中的 Async Clipboard API。在 Glitch 上,您可以对文字演示图片演示进行混剪,以便对其进行实验。

第一个示例演示了如何将文本在剪贴板中移入和移出。

如需使用图片试用此 API,请使用此演示。回想一下,仅支持 PNG 且仅在 几种浏览器

致谢

Asynchronous Clipboard API 由 Darwin 实现 Huang 和 Gary Kačmarčík。Darwin 还提供了演示。 感谢 Kyarik 和 Gary Kačmarčík 审核本文的部分内容。

主打图片由 Unsplash 用户 Markus Winkler 提供。