首页 实用干货 🌋

1. 前言

面向搜索引擎开发时,我们经常看到这样的情况:登录后复制。

由于设置了css属性 user-select:none,此时鼠标无法实现选中文本,也就无法复制文本,通常会采用这种方式来禁止复制文本。打开开发者工具-审查元素,取消此样式,就可以选中文本了。

但是,你们有尝试过这样的复制、粘贴吗?

咦,原来也可以不用登录后复制嘛。??

其实,在记事本、textarea等纯文本框内粘贴是可以通过此属性达到禁止复制的功能,但是在富文本框内粘贴也就无能为力了。

那我们先了解下user-select,然后完善禁止复制的能力。

2. user-select

该属性用来控制用户能否选中文本。其对应常用的值:

  • none:元素及其子元素的文本不可选中。
  • auto:具体取值取决于一系列条件,具体如下:
    • ::before::after 伪元素上,采用的属性值是 none
    • 如果元素是可编辑元素,则采用的属性值是 contain
    • 否则,如果此元素的父元素的 user-select 采用的属性值为 all,则该元素采用的属性值也为 all
    • 否则,如果此元素的父元素的 user-select 采用的属性值为 none,则该元素采用的属性值也为 none
    • 否则,采用的属性值为 text
  • text:用户可以选中文本。
  • all:当单击文本时,会选中这一行文本。

全部值如下:

/* Keyword values */
user-select: none;
user-select: auto;
user-select: text;
user-select: contain;
user-select: all;

/* Global values */
user-select: inherit;
user-select: initial;
user-select: revert;
user-select: revert-layer;
user-select: unset;

3. 选中、复制、粘贴操作

单独选中带有user-select:none的文本样式是无法选中的,但是,我们借用可以选中的文本,虽然中间那个文本是无法看到拖蓝,跨段选中然后复制。

我们发现在富文本框,可以将设置了none的文本正确地粘贴了。前提需要借助可以复制的文本跨段选中。所以前言中“登录后复制”的代码也能使用这种方式,在wps等富文本编辑器中粘贴获得完整代码。

4. user-select:none 如何实现禁止复制文本

在复制文章粘贴时,会发现文本最后会带上版权信息。既然可以悄悄的改动复制的文本内容,那我们也就可以控制哪些文本是否可以复制。

最终我们要实现的效果是,设置了user-select:none的节点,无论在纯文本内还是富文本框内粘贴,都不会粘贴出来,实现了真正的禁止复制,顺便添加版权信息。??

首先,为网页添加copy事件。梦想开始的地方。

采用Selection对象获取到选中的文本,这里面包含了user-select:none节点。

window.addEventListener('copy',()=>{
    const selection = window.getSelection()
    const copyNode = selection.getRangeAt(0).cloneContents();
})

创建一个div,将copyNode放在div内,并挂载到dom树上,以便获取节点的user-select属性值,来控制是否需要复制该节点。

const div = document.createElement('div');
div.appendChild(copyNode);
// 通过getComputedStyle获取style值,需要将节点挂载到dom树上,因此append到body
document.body.appendChild(div);
const nodelist = div.childNodes;
for (let i = 0; i < nodelist.length; i++) {
    const curnode = nodelist[i];
    const nodeType = curnode.nodeType;
    // 重点操作
    // 将节点类型为元素节点,并且设置了user-select:none的节点从复制的内容中过滤掉
    if (nodeType === 1 && window.getComputedStyle(curnode).userSelect === 'none') {
      div.removeChild(curnode);
    }
}
// 获取过滤后的数据
const cpt = div.innerHTML;

将处理好的数据设置为选中,顺便设置个版权信息。

// 又创建个div,将选区设置为此div
const div2 = document.createElement('div');
const copyright = `著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。`
div2.innerHTML = cpt + copyright;
// 要设置选区,需要挂载在dom树上
document.body.appendChild(div2);
// 更新选区
selection.selectAllChildren(div2);

我们创建了2个div,并且append到了页面上,这不应该对用户可见。因此:

  1. 通过设置css,将div放在页面不可见地方
const div2 = document.createElement('div');
+ div2.style.position = 'fixed';
+ div2.style.left = '9999px';
const copyright = `著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。`
  1. 或者在操作最后及时移除节点
// 更新选区
selection.selectAllChildren(div2)
+ setTimeout(() => {
+    document.body.removeChild(div2);
+    document.body.removeChild(div);
+})

以上就是实现完整地禁止复制的全部代码。

5.结语

网上大部分复制文本添加版权的做法是,通过window.getSelection().toString()获取到选中的文本,但是这种方法不会过滤掉设置了user-select:none的文本。

为什么没有通过剪切板去获取复制的文本数据呢? 因为copy事件触发时间是在实际复制操作之前,所以此时剪贴板内还没有数据,曲线救国采用了selection

其次,添加版权信息的做法是,通过window.clipboardData.setData('text', copyNode + copyright)将数据设置到剪贴板上,但是这种方法只能设置纯文本,无法粘贴带样式的文本,但是也可以通过设置新的选区来实现获取富文本数据。

之前在项目里用了此css属性来实现部分敏感信息的禁止复制,自己在开发自测时确实无法选中文本复制,但是上线后,用户反馈文本仍是可以复制并粘贴到同界面的一个富文本框内。后面了解到用户的复制操作是在鼠标拖蓝选中的文本中将不可选中的文本一起复制了,并刚好在富文本框内粘贴,防不胜防啊??。

不过,看到此文的你们一定不会再有此bug!



文章评论

未显示?请点击刷新