copy to paste in Browser

2,536 阅读5分钟

文章首发于语雀 copy to paste in Browser

未经允许严禁转载

前言

故事起源是这样的,在使用语雀编写文档时,从其他应用复制文本到其中,完成复制后,显示内容与平常的粘贴不一样,会带有原文本的样式,然后某天就非常好奇this,然后search and read该类文章,再加上owner实践,所以简单的总结下。

Copy Paste

我们细想一下,在平常,复制粘贴文本时,似乎用不到什么特殊功能,仅仅ctrl +cctrl + v或者command + ccommand + v就可以了。那有没有这样的经历,在掘金上复制一段文字接着在其他处粘贴时,会发现该段文字末尾会带有作者、链接、来源等文本。这是为什么呢?怎么和我们平常的操作不太一样?

ClipboardData

先来介绍一下两个Eventcopypaste。顾名思义,这是提供给我们的复制和粘贴事件。通过注册事件,操作ClipboardData就可以达到效果了。请看👇

document.addEventListener('copy',e => {
  e.preventDefault();
  const clipboardData = e.clipboardData ;
  const packageCopyText = window.getSelection().toString() + '\n\n' 
  + '作者:уúрeìLín' + '\n' 
  + '链接:https://www.yuque.com/yupeilin/haoduoyue/gvagx9' + '\n' 
  + '来源:语雀' + '\n'
  + '著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。';
  clipboardData.setData('text', packageCopyText);
});
document.addEventListener('paste',e => {
  // 得到要复制的文本
  console.log(e.clipboardData.getData('text'));
})

Jan-07-2020 14-19-39.gif

现在来解释上面的每行代码的意思。 e.preventDefault() 取消默认行为。因为一旦不取消,则不会起作用。 e.clipboardData 保存了一个DataTransfer对象,这个对象可用于:

  • 描述哪些数据可以由cutcopy事件处理器放入剪切板,通常通过调用setData(format, data)方法
  • 获取由paste事件处理器拷贝进剪切板的数据,通常通过调用getData(format)方法

window.getSelection() 表示用户选择的文本范围或光标的当前位置,可以通过toString()将其转化为字符串

那除了这种方法还有其他的吗? 异步剪贴板 API(Async Clipboard API),也就是navigator.clipboard。那先来看看怎么使用吧。

navigator.clipboard

Navigatoer接口添加了clipboard只读属性,该属性返回一个可以读写剪切板内容的Clipboard对象。剪切板API可用于在web应用中实现剪切、复制、粘贴的功能。

writeText()可以把文本写入剪切板。writeText()是异步的,它返回一个 Promise。

和复制一样,可以通过调用readText()从剪贴板中读取文本,该函数也返回一个 Promise。

document.querySelector('#btn').addEventListener('click',()=> {
  navigator.clipboard.writeText('这是我要复制的文本').then(() => {
    console.log('ok');
  }).catch((err) => {
    console.log('error',err);
  })
})
// 获取张贴文本
navigator.clipboard.readText().then((text) => {
  console.log('ok',text);
}).catch((err) => {
  console.log('error', err);
})

Jan-07-2020 15-10-41.gif

document.execCommand

如果你的应用需要兼容较老的浏览器,那么得换种方式。因为上述的这两种方式对浏览器的版本是有要求的。既然如此,那就介绍一下通用的方式,document.execCommand。大家想必对这个API不陌生吧,因为这个是制作富文本编辑器的利器。那就简单写下实现代码吧。

// 点击id=btn的按钮
document.querySelector('#btn').addEventListener('click', () => {
  const copyText = '这是我要复制的文本'
  // 创建元素并使其不在浏览视区内显示
  const input = document.createElement('input');
  input.value = copyText;
  input.style.cssText = 'position:absolute;left:-9999px';
  document.body.appendChild(input)
  input.focus();
  input.select();
  // copy
  document.execCommand('copy');
  // 移除元素
  document.body.removeChild(input);
})

好了,介绍了三种复制粘贴的方式。但还是没有提到复制文本和文本样式。别急,接着往下看。

Copy Text and Style

首先考虑一下,复制文本到元素input或者textarea,会带文本样式吗?显然是不会。因为这两种元素已经规定了复制的文本按照元素设定的样式显示,而不是显示文本的自带样式。那应该怎么做呢?

html全局属性中有一个叫做**contenteditable**的属性,表示元素是否可被用户编辑。如果可以,浏览器会修改元素的部件以允许编辑。这样就能达到复制文本和文本样式了。但这样ok了吗?

通过e.clipboardData.getData('text/html'),可以查看这个实际显示文本的html内容。

<meta charset='utf-8'>
<div style="
    color: #bbbbbb;
    background-color: #1e1e1e;
    font-family: Menlo, Monaco, 'Courier New', monospace;
    font-weight: normal;
    font-size: 12px;
    line-height: 18px;
    white-space: pre;"
>
  <div>
    <span style="color: #36f9f6;">
    xxxx
    </span>
  </div>
</div>

显然,这个html内容并不符合我们的需求。因为font-familyfont-weight等css属性其实是可以共用的。那得做个转化,把原html内容转变成我们需要的html内容。

实现步骤如下:

  1. 取消paste事件的默认行为,e.preventDefault()
  2. 获取到实际的html内容,e.clipboardData.getData('text/html')
  3. 转化该html内容,生成一个新的html内容
  4. 创建一个元素,内容为新的html内容,插入到该节点中

这样就能满足实际的需求的,将重复性的css属性以css类名的方式表示,并且将一些原有的样式去除。这就是富文本编辑器的复制功能(没了解过内部的复制功能的实现,上面的实现步骤可能有错误,谢谢指出)。

总结

实现复制粘贴功能的三种方式,前两种用的是Clipboard对象提供的功能,而后一种则是将html文档切换到设计模式,允许通过命令操纵可编辑区域。并且后一种的兼容性更好。

只有元素的属性contenteditable为true,才能复制文本和文本样式。富文本编辑器提供了更为优秀的复制逻辑。

参考:

Clipboard

ClipboardEvent.clipboardData

Async Clipboard API:异步剪贴板 API

前端er怎样操作剪切复制以及禁止复制+破解等

网页内容复制粘贴(三种方案 兼容多种浏览器)