浏览器提供了三个方法来监听复制粘贴行为,分别是copy,cut,paste,可以看到这些方法返回的类型都是ClipboardEvent,这个对象述了与修改剪切板相关信息的事件,其中 clipboardData 是我们需要关注的对象,这里面包含了剪切板的具体数据。先看看 notion 和一般页面复制的内容有什么不同,主要是 types 类型,我们给 body 加上contenteditable="true"
,这样就可以在页面中直接编辑内容了,然后在控制台监听 paste 事件,看看复制的内容是什么,
<!doctype html>
<html lang="ch">
<body contenteditable="true">
<div id="root">test</div>
</body>
<script>
document.addEventListener("paste", function (event) {
var clipboardData = event.clipboardData;
console.log("types", clipboardData.types);
for (const type of clipboardData.types) {
console.log("data", clipboardData.getData(type));
}
for (const item of clipboardData.items) {
console.log("item", item);
}
});
</script>
</html>
从 notion 复制两条内容到浏览,实际输出的内容如下,可以看到输出了很多行,但如果如你看看下 html 结构,实际上浏览器只插入了text/html
的 html 内容。
看到这里其实就可以给出结论了,浏览器会根据不同的输入元素读取不同的类型,比如input
之类的元素会读取text/plain
,而contenteditable
的元素会读取text/html
,这里类型其实就是 media-types,通常这些类型操作系统会自动处理。
而除了一般的 Media type,也可以自定义的类型,例如text/_notion-multi-text-production
,这些自定义的类型浏览器默认是不会读取和操作的,需要手动监听和读取,而通过读取这些自定义类型,就可以实现一开始提到 notion 的复制粘贴操作了,也是可以看到复制粘贴操作携带的数据可能远比我们看到的多。顺便一提。我而在写文章时我发现 vscode 也有类似的自定义类型的。
写入自定义类型
写入自定义类型也很简单,我们需要劫持copy
事件就好。
document.addEventListener("copy", function (event) {
event.preventDefault();
var clipboardData = event.clipboardData!;
clipboardData.setData("text/plain", "Hello, world!");
clipboardData.setData("text/html", "Hello, world!");
clipboardData.setData("text/hello", "Hello, world!");
});
运行上面方法后,会发现复制的内容都成Hello, world!
,同时也增加了一个text/hello
的类型,这样就可以实现自定义类型的复制粘贴了。值得注意的是,因为直接修改复制事件内容是不会生效的,所以我们需要调用event.preventDefault()
来阻止默认行为,然后通过clipboardData.setData
来设置内容,要完全兼容原生的复制粘贴,可能需要考虑到各种细节。