JS67 复制到剪贴板

想要实现这样一个需求,给定一个输入框和按钮,点击按钮时会将输入框的内容复制到剪贴板,方便用户操作。该如何实现呢?

Clipboard API

Clipboard API的接口提供了一种读写操作系统剪贴板的方式,它有四个方法,分别是:

  • read,从剪贴板读取数据,比如图片
  • readText,从剪贴板读取文本
  • write,写入数据(比如图片)到操作系统剪贴板
  • writeText,写入文本到剪贴板

我的需求是实现将文字写入到剪贴板,使用writeText方法即可:

1
const promise = navigator.clipboard.writeText(newClipText)

clipboard是挂在navigator对象下的,接受一个字符串作为参数,返回一个Promise。如果写入成功就会被resolve,如果没有权限则会被reject

很简单,但是它的兼容性不怎么样:

可以看出,IE、Edge、Safari全军覆没,即便FireFox和Chrome也都是很新的版本才支持,所以在生产环境上这个API肯定是不能完全胜任的。

document.execCommand

这个方法可以用来操作可编辑区域(比如鼠标选择的文本)的内容,实现富文本功能时大多会用到这个方法:

1
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)

返回值是一个Boolean,如果是false代表操作不被支持。MDN上提示:注意不要用这个返回值去校验浏览器的兼容性(不太明白原理,如此验证会导致性能问题还是报错?既然不用来验证,返回值还有什么用?

它有三个参数:

(1)第一个参数是一个字符串,是要执行的命令的名称。能够执行的命令非常多,不仅仅包括我们后面会用到的将选中内容复制到剪贴板的copy方法,还有例如fontSizeinsertImagecut等方法,具体可以参考命令列表

(2)第二个参数是一个Boolean,表示是否展示用户界面,一般为false,因为Mozilla没有实现这个功能

(3)第三个参数是给一些命令使用的额外的参数,默认为null

想要用它实现复制输入框内容,必须有可编辑区域,比较常用的就是input(或者是contenteditabletrue的区域),同时必须将可编辑区域中的内容,如果使用的容器是input,那么需要让inputfocus,然后通过inputselect方法选中输入框中的内容(如果是contenteditabletrue时则需要使用window.getSelection获得用户选择的文本范围或光标位置)

在我们的需求中,可以利用现成的input来当做容器,但是如果是作为通用的方法,如果用户双击一段文本后复制,那么就需要自己动手创建一个input来实现。但是这个input应该是隐藏的,不应该被用户发现,但是如果使用dispaly: none是不行的,这样是没有办法获取到input中的文本(用type="hidden"也不行),所以我采用的是绝对定位,把input定位到屏幕外,眼不见心不烦。

结合上面的Clipboard API,实现了一个公共方法(其中的Message是一个用来提示的方法)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 将文字复制到系统剪切板
* @param { string } text 复制到剪切板的文字
*/
export function copyTextToClipboard(text) {
// 支持 Clipboard API 并且在安全环境下(localhost 或者 https)
if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
navigator.clipboard.writeText(text).then(() => {
Message({
message: '内容已复制到剪切板',
type: 'success',
duration: 1000,
});
}).catch(() => {
// 静默失败
});
} else if (document && document.execCommand && typeof document.execCommand === 'function') {
// 使用 execCommand 的后退方案
const input = document.createElement('input');
input.style.cssText = 'position: absolute; left: -9999px; top: -9999px; z-index: -9999;';
input.readOnly = true;
document.body.appendChild(input);
input.value = text;
input.focus();
input.select();
const isSucceed = document.execCommand('copy', true);
document.body.removeChild(input);
if (isSucceed) {
Message({
message: '内容已复制到剪切板',
type: 'success',
duration: 1000,
});
} else {
// 静默失败
}
}
}

document.execCommand的兼容性还是相当不错的:

所以上面的方法基本上可以满足生产环境的要求了。

clipboard.js

实际上有现成的库可以使用,那就是clipboard.js,官网在这里,Github的Star数和NPM的下载数都很高,并且压缩后体积只有3KB,直接使用应该是没有什么问题的。

它可以从一个元素复制、剪切文本,也可以从性中来复制文本,比如在Vue中我们可以直接使用在一个Button元素上,通过v-bind绑定一个响应式数据在data-clipboard-text属性上,就可以实现点击Button进行复制的功能

1
<button class="btn" :data-clipboard-text="copyValue">Copy</button>

它还有对应的成功或者失败的回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var clipboard = new ClipboardJS('.btn');

clipboard.on('success', function(e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);

e.clearSelection();
});

clipboard.on('error', function(e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
});

更多的细节和高级用法可以参考它的官网

它主要使用就是SelectionexeceCommand这两个API,所以兼容性也是很不错的:

它的源码也不是很复杂,可以阅读这篇文章对源码的解读,其实自己去读一下也是可以的。

总结

所以,如果需要实现的功能不太复杂,那么上面自己封装的函数应该可以满足需求,如果需要频繁使用或者需要一些高级功能,那么可以直接使用clipboard.js

参考