上周接着优化我的设计器项目,其中复制粘贴这个小功能就花了大约两天时间,修复了一些问题,并且优化了使用上的体验,这篇文章就简单聊聊此功能的心路历程。

CV 是很常见且使用频率较高的快捷操作,本项目中复制粘贴的使用场景主要有以下几种:

  1. 直接粘贴文字
  2. 直接粘贴图片
  3. 复制画布中的元素粘贴
  4. 在编辑文字组件时粘贴文本

第三种项目中已经实现,最后一种是系统剪贴板自带的功能,不需要实现,主要实现的是前面两种,也就是读取系统剪贴板的操作

我们可以使用 Clipboard API 来调用剪贴板,调用时需要明确获得用户的许可。

系统剪贴板是属于托管浏览器的操作系统的数据缓冲区,用于文档或应用程序之间的短期数据存储或传输。事件是由于 cut、copy、paste 操作修改剪贴板而触发的。另一个访问剪贴板的方法:document.execCommand() 已被标记为弃用。

Clipboard API 的所有操作都是异步的,返回 Promise 对象,不会造成页面卡顿。而且,它可以将任意内容(比如图片)放入剪贴板中,以下是简单的读取和判断代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
navigator.clipboard
.read()
.then(async (dataTransfer) => {
if (store.getters.dActiveElement.editable) {
return;
}
for (let i = 0; i < dataTransfer.length; i++) {
const item = dataTransfer[i];
if (item.types.toString().indexOf("image") !== -1) {
const imageBlob = await item.getType(item.types[0]);
const file = new File([imageBlob], "screenshot.png", {
type: "image/png",
});
// TODO: 上传图片 file
// TODO: 添加图片到画布中
break;
} else if (item.types.toString().indexOf("text") !== -1) {
// TODO: 添加文字到画布中
break;
}
}
})
.catch((error) => {
// 剪贴板内容为空
});

可以使用 navigator.clipboard.writeText('') 来清空系统剪贴板的文字,但如果复制的是图片,数据就清除不掉,所以我通过伪造一个空字符数据来实现清空剪贴板图片的效果:

1
2
3
4
5
navigator.clipboard.write([
new ClipboardItem({
"text/plain": new Blob([""], { type: "text/plain" }),
}),
]);

文字 CV 效果如下,可将任意文字内容复制粘贴进画布。

由于图片自动上传,不会触发用户图片列表的刷新,一开始我想干脆把图片列表放进全局状态管理,但很快发现这么做很麻烦,虽然图片列表的数据可以在其它地方更新了,但是它还有分页数要处理(keepalive 的组件),所以最后还得是事件广播的方式来解决最好。

由于 Vue3 中已经移除了全局的 eventBus 功能,所以安装了官方推荐的 mitt 这个库来实现。

https://github.com/developit/mitt

粘贴之后还要将新的元素再复制一遍,这也是一个小优化点,体现无限复制下去的效果。

最后还有一个小细节:每次粘贴图片,都会触发上传,稍不注意就会上传许多重复的图片,所以这里我也做了相应的优化,从系统剪贴板读取的图片数据会在粘贴一次后转化为画布的图片元素,此时再自动执行一次复制,这样多次执行粘贴操作只会上传一次图片

如果不做这个优化会是怎么样的呢?下面有请我们的老朋友稿定设计来演示下:

接着我也看了 Canva(可画)的表现,看来业界都是相当统一,不过可画这边的上传队列是非异步的,所以不停粘贴的话甚至会卡住,就是一定得等前面的图片上传完成(我这里是连续按 Ctrl V 的):

另外可画这个上传时的水位图动画蛮有趣,以前一直觉得他家界面有点土,然而这种小细节做出来着实平添了几分科技感,值得学习。

创客贴的也体验了一下,实在太粗糙,就不放演示了😅。

最后欢迎访问迅排设计在线体验,文章有点水,纯当一个小分享,希望对你有所帮助。