文章首发于语雀 copy to paste in Browser
未经允许严禁转载
前言
故事起源是这样的,在使用语雀编写文档时,从其他应用复制文本到其中,完成复制后,显示内容与平常的粘贴不一样,会带有原文本的样式,然后某天就非常好奇this,然后search and read该类文章,再加上owner实践,所以简单的总结下。
Copy Paste
我们细想一下,在平常,复制粘贴文本时,似乎用不到什么特殊功能,仅仅ctrl +c
、 ctrl + v
或者command + c
、command + v
就可以了。那有没有这样的经历,在掘金上复制一段文字接着在其他处粘贴时,会发现该段文字末尾会带有作者、链接、来源等文本。这是为什么呢?怎么和我们平常的操作不太一样?
ClipboardData
先来介绍一下两个Event,copy和paste。顾名思义,这是提供给我们的复制和粘贴事件。通过注册事件,操作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'));
})
现在来解释上面的每行代码的意思。
e.preventDefault()
取消默认行为。因为一旦不取消,则不会起作用。
e.clipboardData
保存了一个DataTransfer对象,这个对象可用于:
- 描述哪些数据可以由
cut
和copy
事件处理器放入剪切板,通常通过调用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);
})
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-family
、font-weight
等css属性其实是可以共用的。那得做个转化,把原html内容转变成我们需要的html内容。
实现步骤如下:
- 取消
paste
事件的默认行为,e.preventDefault()
- 获取到实际的html内容,
e.clipboardData.getData('text/html')
- 转化该html内容,生成一个新的html内容
- 创建一个元素,内容为新的html内容,插入到该节点中
这样就能满足实际的需求的,将重复性的css属性以css类名的方式表示,并且将一些原有的样式去除。这就是富文本编辑器的复制功能(没了解过内部的复制功能的实现,上面的实现步骤可能有错误,谢谢指出)。
总结
实现复制粘贴功能的三种方式,前两种用的是Clipboard
对象提供的功能,而后一种则是将html文档切换到设计模式,允许通过命令操纵可编辑区域。并且后一种的兼容性更好。
只有元素的属性contenteditable
为true,才能复制文本和文本样式。富文本编辑器提供了更为优秀的复制逻辑。
参考: