操作剪贴板 navigator.clipboard

269 阅读4分钟

=======copy/paste======== 页面内容如下:

阳光hello

1 2 3 4 5

选择所有内容:

user-select 按钮左右两侧的文字背景都变蓝了,但是按钮没有变。这是因为按钮的 user-select 默认值是 none

如果希望按钮也可以选择,手动设置 user-select:

input[type="button"] { user-select: text; } 1 2 3 此时再全选:

禁止复制 通过阻止默认事件来实现禁止复制

// 禁止复制(ctrl+c ctrl+x shift+insert) container1.addEventListener("copy", (e) => {e.preventDefault();}); // 禁止右键菜单 container1.addEventListener("contextmenu", (e) => {e.preventDefault();}); // 禁止选中文字 container1.addEventListener("selectstart", (e) => {e.preventDefault();}); 1 2 3 4 5 6 在 copy 事件中获取内容 在 copy 事件中获取 selection

container1.addEventListener("copy", (e) => { const sel = window.getSelection(); console.log(sel); }); 1 2 3 4

selection.toString 获取选择区域中的纯文本

container1.addEventListener("copy", (e) => { const sel = window.getSelection(); console.log(sel.toString()); // 输出'阳光hello' }); 1 2 3 4 输出: 阳光hello(注意:虽然给按钮设置了 user-select,但是 toString() 中还是没有按钮的文本!)

如果要获取选中内容的 html 格式,用 cloneContents 获取 html

container1.addEventListener("copy", (e) => { const sel = window.getSelection(); // console.log(sel().toString()); // 输出'阳光hello' const range = sel.getRangeAt(0); // TODO: getRangeAt(0) 什么意思 console.log(range.cloneContents()); // 输出 document-fragment }); 1 2 3 4 5 6 输出:

在 copy 事件中修改内容 通过 e.clipboardData.setData 修改剪贴板内容(必须阻止 copy 默认事件)

container1.addEventListener("copy", (e) => { const sel = window.getSelection(); const text = sel.toString(); e.clipboardData.setData("text/plain", text + "-后缀"); e.preventDefault(); // 必须要阻止默认事件 }); 1 2 3 4 5 6 在 paste 中获取内容 通过 e.clipboardData.getData 获取内容

container1.addEventListener("paste", (e) => { // console.log(e.clipboardData.types); // 输出 ['text/plain', 'text/html'] console.log(e.clipboardData.getData("text/plain")); console.log(e.clipboardData.getData("text/html")); }); 1 2 3 4 5 输出:

windows 系统用 clipboardData 获取 html 数据时,会自动包一层 html>body 和 startFragment,这个”特性“造成了 prosemirror 项目的 bug —— fix: prosemirror adds two extra spaces when paste。

下一篇文章会讲到用 navigator.clipboard.read 获取数据,用那个 API 拿到的 html 数据是纯净的,不会包 html>body 和 startFragment 标签。

在 paste 中修改内容 在 paste 阶段再想修改内容很麻烦,但有时候不得不这么做。

如果想要在用户从网站上复制内容时,末尾都追加上版权信息。这种情况可以在 copy 方法中修改内容。 但是如果在用户进行粘贴操作时,需要根据不同的目标区域有不同的修改策略,那么就只能在 paste 方法中处理了。

举例:

Document

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

希望在粉色区域粘贴时,加上后缀”-后缀1”。在灰色区域粘贴是,加上后缀“-后缀2”

要实现这个效果,只能阻止默认事件,然后自己执行 insertHTML

target1.addEventListener("paste", (e) => { const text = e.clipboardData.getData("text/plain"); document.execCommand("insertHTML", false, text + "-后缀1"); e.preventDefault(); }); target2.addEventListener("paste", (e) => { const text = e.clipboardData.getData("text/plain"); document.execCommand("insertHTML", false, text + "-后缀2"); e.preventDefault(); }); 1 2 3 4 5 6 7 8 9 10 效果:

———————————————— ======================== navigator.clipboard================================ 使用 navigator.clipboard 可以随时获取剪贴板对象(也就是说,在 copy/paste 事件外也可以用)

但是,此操作必须用户允许:

readText readText() 获取剪贴板中的文本内容

Document

文字hello

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

选择 div 中的全部内容,复制

点击“查看剪切板文本按钮”,控制台输出:文字hello

read read() 获取剪贴板内容

async function getClipboard() { const clipboardItems = await navigator.clipboard.read(); for (const clipboardItem of clipboardItems) { const types = clipboardItem.types; // console.log("types", types); // 输出 ['text/plain', 'text/html'] for (const type of types) { const blob = await clipboardItem.getType(type); const blobText = await blob.text(); console.log(type, blob, blobText); } } } 1 2 3 4 5 6 7 8 9 10 11 12 输出结果:

上图的含义是:剪贴板中有两类数据:text/plain(纯文本) 、 text/html(文本格式存储的html) 纯文本数据内容是:文字hello html 数据内容是:文字hello(省略style属性)

writeText async function setClipboard() { await navigator.clipboard.writeText("你好"); } 1 2 3 write

Document
<input type="button" value="设置剪贴板" onclick="setClipboard()" />
<script>
  async function setClipboard() {
    await navigator.clipboard.write([
      new ClipboardItem({
        "text/html": new Blob([`<span style="background:red">haha</span>`], {
          type: "text/html",
        }),
      }),
    ]);
  }
</script>

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 为了方便演示效果,准备一个 contenteditable= true 的 div:

点击“设置剪贴板"按钮,然后粘贴到 div 中,结果: