鼠标一键点击实现页面文本全选和复制

需求背景

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

自动全选.gif

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

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

技术方案实现的思路:

  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进行处理。

    document.addEventListener('copy', async (e) => {
      e.preventDefault();
      try {
        let clipText = ''
        ...
        // 将自定义的内容放入剪贴板中 
        e.clipboardData.setData('text/plain', clipText)
      } catch (err) {
        console.log(err);
      }
   });

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

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

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

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

自动复制.gif

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

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

引用

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