面向搜索引擎开发时,我们经常看到这样的情况:登录后复制。
由于设置了css属性 user-select:none
,此时鼠标无法实现选中文本,也就无法复制文本,通常会采用这种方式来禁止复制文本。打开开发者工具-审查元素,取消此样式,就可以选中文本了。
但是,你们有尝试过这样的复制、粘贴吗?
咦,原来也可以不用登录后复制嘛。
其实,在记事本、textarea
等纯文本框内粘贴是可以通过此属性达到禁止复制的功能,但是在富文本框内粘贴也就无能为力了。
那我们先了解下user-select
,然后完善禁止复制的能力。
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;
单独选中带有user-select:none
的文本样式是无法选中的,但是,我们借用可以选中的文本,虽然中间那个文本是无法看到拖蓝,跨段选中然后复制。
我们发现在富文本框,可以将设置了none
的文本正确地粘贴了。前提需要借助可以复制的文本跨段选中。所以前言中“登录后复制”的代码也能使用这种方式,在wps等富文本编辑器中粘贴获得完整代码。
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 = `
作者:Yue栎廷
来源:https://juejin.cn/user/3421335917437134掘金@Yue栎廷
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
`
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 = `
作者:Yue栎廷
来源:掘金@Yue栎廷
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
`
2.或者在操作最后及时移除节点
// 更新选区
selection.selectAllChildren(div2)
+ setTimeout(() => {
+document.body.removeChild(div2);
+document.body.removeChild(div);
+})
以上就是实现完整地禁止复制的全部代码。
网上大部分复制文本添加版权的做法是,通过window.getSelection().toString()
获取到选中的文本,但是这种方法不会过滤掉设置了user-select:none
的文本。
为什么没有通过剪切板去获取复制的文本数据呢? 因为copy事件触发时间是在实际复制操作之前,所以此时剪贴板内还没有数据,曲线救国采用了selection
。
其次,添加版权信息的做法是,通过window.clipboardData.setData('text', copyNode + copyright)
将数据设置到剪贴板上,但是这种方法只能设置纯文本,无法粘贴带样式的文本,但是也可以通过设置新的选区来实现获取富文本数据。
之前在项目里用了此css属性来实现部分敏感信息的禁止复制,自己在开发自测时确实无法选中文本复制,但是上线后,用户反馈文本仍是可以复制并粘贴到同界面的一个富文本框内。后面了解到用户的复制操作是在鼠标拖蓝选中的文本中将不可选中的文本一起复制了,并刚好在富文本框内粘贴,防不胜防啊。
不过,看到此文的你们一定不会再有此bug!
为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。
有需要的小伙伴,可以点击下方卡片领取,无偿分享