Canvas简历编辑器-我的剪贴板里究竟有什么数据
在这里我们先来聊聊我们究竟应该如何操作剪贴板,也就是我们在浏览器的复制粘贴事件,并且在此基础上聊聊我们在Canvas
图形编辑器中应该如何控制焦点以及如何实现复制粘贴行为。
- 在线编辑: https://windrunnermax.github.io/CanvasEditor
- 开源地址: https://github.com/WindrunnerMax/CanvasEditor
关于Canvas
简历编辑器项目的相关文章:
- 社区老给我推Canvas,我也学习Canvas做了个简历编辑器
- Canvas图形编辑器-数据结构与History(undo/redo)
- Canvas图形编辑器-我的剪贴板里究竟有什么数据
- Canvas简历编辑器-图形绘制与状态管理(轻量级DOM)
- Canvas简历编辑器-Monorepo+Rspack工程实践
剪贴板
我们在平时使用一些在线文档编辑器的时候,可能会好奇一个问题,为什么我能够直接把格式复制出来,而不仅仅是纯文本,甚至于说从浏览器中复制内容到Office Word
都可以保留格式,看起来是不是一件很神奇的事情,不过当我们了解到剪贴板的基本操作之后,就可以了解这其中的底层实现了。
说到剪贴板,我们可能以为我们复制的就是纯文本,当然显然光靠复制纯文本我们是做不到这一点的,所以实际上剪贴板是可以存储复杂内容的,那么在这里我们以Word
为例,当我们从Word
中复制文本时,其实际上是会在剪贴板中写入这么几个key
值:
text/plain
text/html
text/rtf
image/png
看着text/plain
是不是很眼熟,这明显就是我们常见的Content-Type
或者称作MIME-Type
,所以说我们是不是可以认为剪贴板是一个Record<string, string>
的类型,但是别忽略了我们还有一个image/png
类型,因为我们的剪贴板是可以复制文件的,所以我们常用的剪贴板类型就是Record<string, string | File>
,例如此时复制这段文字在剪贴板中就是如下内容。
text/plain
例如此时复制这段文字在剪贴板中就是如下内容
text/html
<meta charset='utf-8'><strong style="...">例如此时复制这段文字</strong><em style="...">在剪贴板中就是如下内容</em>
那么我们粘贴的时候就很明显了,我们只需要从剪贴板里读取内容就可以了,例如我们从语雀复制内容到飞书中,我们在语雀复制的时候会将text/plain
以及text/html
写入剪贴板,在粘贴到飞书的时候就可以首先检查是否有text/html
的key
,如果有的话就可以读取出来,并且将其解析成为飞书自己的私有格式,就可以通过剪贴板来保持内容格式粘贴到飞书了,如果没有text/html
的话,就直接将text/plain
的内容写到私有的JSON
数据即可。
此外,我们还可以考虑到一个问题,在上边的例子中实际上我们是复制时需要将JSON
转到HTML
字符串,在粘贴时需要将HTML
字符串转换为JSON
,这都是需要进行序列化与反序列化的,是需要有性能消耗以及内容损失的,所以是不是能减少这部分消耗,那么当然是可以的,通常来说如果是在应用内直接直接粘贴的话,可以直接通过剪贴板的数据直接compose
到当前的JSON
即可,这样就可以更完整地保持内容以及减少对于HTML
解析的消耗。例如在飞书中,会有docx/text
的独立Clipboard Key
以及data-lark-record-data
作为独立JSON
数据源。
那么至此我们已经了解到剪贴板的工作原理,紧接着我们就来聊一聊如何进行复制操作,说到复制我们可能通常会想到clipboard.js
,如果需要兼容性比较高的话可以考虑,但是如果需要在现在浏览器中使用的话,则可以直接考虑使用HTML5
规范的API
完成,在浏览器中关于复制的API
常用的有两种,分别是document.execCommand("copy")
以及navigator.clipboard.write
。
对于document.execCommand("copy")
来说,我们可以直接借助textarea + execCommand
来执行写剪贴板的操作,在这里需要注意的是如果这个事件必须要是isTrusted
的事件,也就是说这个事件必须要是用户触发的,例如点击事件、键盘事件等等,如果我们在打开页面后直接执行这段代码的话,则实际上是不会触发的。此外,如果在控制台执行这段代码的话,写入剪贴板是可行的,因为我们通常会用回车这个操作来执行代码,所以这个事件是isTrusted
的。
const TEXT_PLAIN = "text/plain";
const data = {
"text/plain": "1", "text/html":"<div>1</div>"};
const textarea = document.createElement("textarea");
textarea.addEventListener(
"copy",
event => {
for (const [key, value] of Object.entries(data)) {
event.clipboardData && event.clipboardData.setData(key, value);
}
event.stopPropagation();
event.preventDefault();
},
true
);
textarea.style.position = "fixed";
textarea.style.left = "-999px";
textarea.style.top = "-999px";
textarea.value = data[TEXT_PLAIN];
document.body.appe