需求背景

有些页面上的数据用户经常需要全选然后进行复制的工作,用户一般需要两步操作,第一步是选中文本,用户可以通过三次点击或着点击后拖动鼠标来进行选中,第二步则是点击右键选择复制,或者利用键盘快捷键。为了提高用户体验,我们想要让用户点击相关文字时候,自动全选并且完成复制操作。

全选复制 - 图1

1. 文本全选实现方案

1.1 利用CSS user-select 属性

CSS属性 user-select 控制用户能否选中文本

auto | text | none | contain | all

contain 在很多浏览器中例如Chrome中时不支持的,在这里我们用 user-selet: all 就能够实现文本全选的功能。

优势: 可以非常灵活,而且十分简单,一个语句就可完成功能。

不足: 作为优化需求的时候,需要修改原有代码。也就是如果要在原来的已经写好的页面中加入此功能,需要找到每一个具体的位置,然后添加对应的className或者增加css的行间样式。

1.2 利用Javascript光标对象selection

selection 代表了当前激活选中区,即高亮文本块,和/或文档中用户可执行某些操作的其它元素。

  1. export default () => {
  2. // 监听鼠标的click事件
  3. document.addEventListener('click', (e: Event) => {
  4. // 获取到鼠标点击的dom元素
  5. const span = e.target;
  6. // 获取dom元素中的的innerText的值
  7. const value = (span as HTMLElement).innerText;
  8. // 遍历所有配置的正则表达式
  9. for (const reg of Object.values(regObject)) {
  10. if (reg.test(value)) {
  11. // 验证通过了正则的值是否是span元素或者是div元素,因为不想在输入框也有此功能
  12. if ((span as HTMLElement).localName === ('span' || 'div')) {
  13. // 获取一个Selection对象
  14. const selection = window.getSelection();
  15. // 先清空一下以选择的内容
  16. selection?.removeAllRanges();
  17. // 创建一个range对象
  18. const range = document.createRange();
  19. // 让range对象包含一个node的内容
  20. range.selectNodeContents(span as HTMLElement);
  21. // 向selection中添加一个区域(range)
  22. selection?.addRange(range);
  23. }
  24. }
  25. }
  26. });
  27. };

技术方案实现的思路:

  1. 监听鼠标click事件
  2. 定义文本的正则匹配规则,例如此需求中是为了全选Location ID,正则为/^[A-Z]{3}-[A-Z](-[0-9]{1,2})+$/, 此正则表达式用到的符号的意思为:

^ 表示(脱字符)匹配开头,在多行匹配中匹配行开头
[A-Z] 等价于所有大写字母
{m} 等价于{m,m},表示出现m次
[0-9] 等价于所有数字
{1,2} 等价于前面的内容出现一到两次
+等价于{1,},表示出现至少一次
$(美元符号)匹配结尾,在多行匹配中匹配行结尾

  1. 如果文本通过正则校验,那么再判断元素的类型,此例子中只希望获取div和span元素中的文本,所以利用了 localName 这个属性来判断元素类型了
  2. 一系列校验完成后,我们获取 selection 对象,利用 removeAllRanges 清空一下内容(相当于初始化
  3. 再通过 createRange 获取range对象,利用 range 对象的 selectNodeContents 来获取范围
  4. 最后使用 addRange 来让文本信息高光

优势: 可以在App.vue入口文件一次添加,不需要修改原有代码

不足: 所需要高亮的数据需要有一定的结构特点,比如数据需要有一定的规则。还有就是全局添加click的监听事件,会增加一点性能消耗

文本全选实现方案总结

两种方案都有优缺点,需要结合具体的场景,需求来选取合适的方案,本文也只是提供一种思路的参考。

2. 自动触发复制的实现方案

实现自动将内容复制到剪贴板中有三种方法:

  • Document.execCommand()方法
  • 异步的 Clipboard API
  • copy事件和paste事件

2.1 Document.execCommand()方法

Document.execCommand() 是操作剪贴板的传统方法,各种浏览器都支持。

它支持复制、剪切和粘贴这三个操作。

document.execCommand(‘copy’)(复制)
document.execCommand(‘cut’)(剪切)
document.execCommand(‘paste’)(粘贴)

Document.execCommand() 方法的缺点是只能复制高亮文本的内容,不可以自定义信息放入剪贴板中, 而且这种方式只支持同步操作。但是对于我们这个需求,这种方式已经足够了。

2.2 异步的 Clipboard API

Clipboard 对象的所有操作都是异步的,返回 Promise 对象,不会造成页面卡顿。

const clipboardObj = navigator.clipboard;

而且,它可以将任意内容(比如图片)放入剪贴板。拥有以下4个方法:

Clipboard.readText() 方法用于复制剪贴板里面的文本数据。
Clipboard.read() 方法用于复制剪贴板里面的数据,可以是文本数据,也可以是二进制数据
Clipboard.writeText()方法用于将文本内容写入剪贴板
Clipboard.write()方法用于将任意数据写入剪贴板,可以是文本数据,也可以是二进制数据

这样的操作更加灵活,高效。但是缺点是 Chrome 浏览器规定,只有 HTTPS 协议的页面才能使用这个 API, 而我们这个项目不是这种协议的,所以无法使用此 API。

2.3 copy事件

此方法需要首先利用2.1中的document.execCommand('copy')方法触发一个复制操作,然后利用监听copy进行处理。

  1. document.addEventListener('copy', async (e) => {
  2. e.preventDefault();
  3. try {
  4. let clipText = ''
  5. ...
  6. // 将自定义的内容放入剪贴板中
  7. e.clipboardData.setData('text/plain', clipText)
  8. } catch (err) {
  9. console.log(err);
  10. }
  11. });

此需求并不是这种方法的应用场景,此方法的应用场景如下:

当你希望自定义放入一些内容在剪贴板中,但是你的项目又并不是 HTTPS 的协议,就只有使用这种方法了。但需要注意一点, 由于 2.1 Document.execCommand()方法 是只支持同步的,所以如果在异步操作里面调用是没有用的,那么如果你想放入剪贴板中的内容是需要异步请求的,再请求之后想要直接调用复制操作的话就会失效。举代码例子:

  1. async getData () {
  2. try {
  3. await this.getClipboardData();
  4. // 这里的复制操作触发是无效的
  5. document.execCommand("Copy")
  6. }
  7. }

所以交互需变成,先保存异步回来的数据,等数据回来以后,再通过一个按钮或其他方式,同步出发复制操作。

全选复制 - 图2

自动触发复制的实现方案总结

三种方式都有其适合的应用场景,但第三种 copy 事件的方式会污染真正的复制操作,还是不太建议使用,只能算是一种兜底的方案。

引用

剪贴板操作 Clipboard API 教程
MDN: user-select
MDN: Selection
MDN: Document.createRange()
JS正则表达式完整教程
javascript实现鼠标点击自动选中点击元素内的文字