取消屏蔽剪贴板访问权限

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

访问系统剪贴板的传统方式是 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);
  }
}

浏览器支持

  • 66
  • 79
  • 63
  • 13.1

来源

write()

实际上,writeText() 只是通用 write() 的一种便捷方法 方法,这样还可以将图片复制到剪贴板。与writeText()类似, 是异步的,并返回 Promise。

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

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

接下来,将 ClipboardItem 对象数组作为参数传递给 write() 方法。目前,您一次只能传递一张图片,但我们希望将 未来将支持多张图片ClipboardItem 接受具有以下特征的对象: 将图片的 MIME 类型用作键,将 blob 用作值。blob 专用 从 fetch()canvas.toBlob() 获取的对象,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);
}

或者,您也可以向 ClipboardItem 对象写入 promise。 对于此模式,您需要事先知道数据的 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);
}

浏览器支持

  • 66
  • 79
  • 127
  • 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 事件:

浏览器支持

  • 1
  • 12
  • 22
  • 3

来源

对于 ClipboardItem

浏览器支持

  • 76
  • 79
  • 127
  • 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);
  }
}

浏览器支持

  • 66
  • 79
  • 125
  • 13.1

来源

read()

navigator.clipboard.read() 方法也是异步的,并返回 。如需读取剪贴板中的图片,请获取 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);
  }
}

浏览器支持

  • 66
  • 79
  • 127
  • 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());
});

浏览器支持

  • 3
  • 12
  • 3.6
  • 4

来源

粘贴事件

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

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

浏览器支持

  • 1
  • 12
  • 22
  • 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。为防止滥用,请仅在页面 当前打开的标签页使用中的标签页中的页面可以在不向剪贴板中写入内容的情况下 请求权限,但从剪贴板读取内容始终需要 权限。

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-readclipboard-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 You 可以对文字演示进行混剪 或图片演示 进行相应的实验

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

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

致谢

Asynchronous Clipboard API 由 Darwin 实现 Huang 和 Gary Kačmarčík。Darwin 也提供了演示。 再次感谢 Kyarik 和 Gary Kačmarčík 查看本文的各个部分。

Markus Winkler 的主打图片 不启动