浏览器剪贴板API的应用

2,696 阅读3分钟

剪贴板在生产力平台上有很高的使用频率。剪贴板API在主流的浏览器上也已经有了不错的支持率。基于前端技术的生产力工具就需要考虑接入这些的能力。

roughwin.github.io/clipboard-d…

技术上可以实现的场景有:

  • 在onpaste事件读取剪贴板内容
  • 在oncopy事件中可以阻止默认行为、写入自定义数据到剪贴板……
  • 在onclick等事件中,可以用js程序执行剪贴板写入

这些技术场景可以支持到的业务场景比如:

  • word文本粘贴到页面(支持plain text、html、rtf等格式)
  • 图片、音频、视频文件粘贴到网页
  • 用户复制文本时,加入版权提示
  • 禁止用户复制文本、复制行为的检测
  • 点击按钮复制数据到剪贴板
  • 复制react组件的状态到另一个组件

接下来就来看看具体的实现

复制外部资源到浏览器

在浏览器的onpaste事件中,可以取到一个DataTransfer类型的字段event.clipboardData。通过它就可以获取的粘贴进浏览器的资源信息。我们把这个过程封装成通用的函数。

function readDataTransferItemAsString(item) {
  return new Promise(function (resolve) {
    item.getAsString(resolve);
  })
}
async function getClipboardData(data) {
  const { items } = data;
  const result = {};
  await Promise.all([...items].map(async item => {
    console.log(item)
    const { type, kind } = item;
    if (kind === 'string') {
      result[type] = await readDataTransferItemAsString(item);
    } else if (kind === 'file') {
      result[type] = item.getAsFile();
    }
  }))
  console.log(result)
  return result;
}
function handlePaste(e) {
  getClipboardData(e.clipboardData).then(setClipInfo)
}

document.removeEventListener('paste', handlePaste);

在OneNote复制一段文本到浏览器:

可以看到,粘贴进来的信息同时包括了text/plain、text/html、text/rtf、image/png三种格式,可以根据自己的业务需要选择使用。

阻止并替换浏览器的默认行为

有时候我们希望保护页面内容不被复制。可以在document上监听oncopy事件。例如:

function handleCopy(e) {
  e.preventDefault()
  const { clipboardData } = e
  clipboardData.clearData();
  clipboardData.setData('text/plain', '版权保护中')
}
document.addEventListener('copy', handleCopy)

这时但我们在页面上选择文本并复制时:

默认行为被改写了,粘贴板被清空并填入了我们设置好的文案。

点击按钮复制

function handleClick(e) {
  function handleCopy(e) {
    e.preventDefault()
    const { clipboardData } = e
    clipboardData.clearData();
    const msg = `code: ${Math.random().toString().slice(2, 10)}\ncopied at: ${new Date().toISOString()}`;
    clipboardData.setData('text/plain', msg);
    document.removeEventListener('copy', handleCopy);
    setResult(msg)
  }
  document.addEventListener('copy', handleCopy)
  document.execCommand('copy');
}

在Demo2点击按钮,就自动把一段文本写入了剪贴板

注意:document.execCommand('copy');这个操作只在特定的事件(比如onClick)响应函数内才会生效。

复制一个组件的状态到另一个组件

我写了两个React组件,其中剪贴板相关的状态逻辑封装成了useClipboardData

import { useState, useEffect } from 'react';

function readDataTransferItemAsString(item) {
  return new Promise(function (resolve) {
    item.getAsString(resolve);
  })
}

async function getClipboardData(data) {
  const { items } = data;
  const result = {};
  await Promise.all([...items].map(async item => {
    console.log(item)
    const { type, kind } = item;
    if (kind === 'string') {
      result[type] = await readDataTransferItemAsString(item);
    } else if (kind === 'file') {
      result[type] = item.getAsFile();
    }
  }))
  console.log(result)
  return result;
}

export function useClipboardState(initData) {
  const [data, setData] = useState(initData);
  useEffect(function () {
    async function handlePaste(e) {
      const d = await getClipboardData(e.clipboardData);
      if (d && d['text/custom']) {
        try {
          setData(JSON.parse(d['text/custom']))
        } catch (e) {
          console.log('paste failed.')
        }
      }
    }
    function handleCopy(e) {
      e.preventDefault()
      const { clipboardData } = e
      clipboardData.clearData();
      clipboardData.setData('text/custom', JSON.stringify(data))
    }
    document.addEventListener('copy', handleCopy)
    document.addEventListener('paste', handlePaste);
    return function () {
      document.removeEventListener('paste', handlePaste);
      document.removeEventListener('copy', handleCopy)
    }
  }, [data]);
  return [data, setData]
}

在Demo3页面可以按下Ctrl+C复制它的状态到剪贴板,到Demo4页面按下Ctrl+V粘贴。同时,序列化以后的状态信息也支持通过邮件、聊天框等工具发送。这种交互逻辑可以极大的方便用户操作。

最后

以上就是我总结的几个剪贴板API应用场景。这些API已经在我负责的项目(富文本编辑、试卷排版系统)中投入使用,并获得了用户好评。有需要的同学可以参考。