先告诉你结论:防不住,只能添堵
前端拦不住复制,能做的只是把复制这件事变得更麻烦。麻烦到什么程度取决于你愿意牺牲多少用户体验,以及对方有多想拿到你的内容。
我把市面上能见到的防复制手段做成了一个实验页面,每种技术都是一个开关,你可以亲手打开,然后亲手绕过:
下面把这些手段从最弱到最强讲一遍,每一种都说清楚怎么写、能拦住谁、怎么破。
添加图片注释,不超过 140 字(可选)
先理解一件事:能看见就能拿到
防复制的根本困境在这里。
浏览器要把内容显示给用户看,就必须把它渲染成屏幕上的像素。用户能看见,就能截图;截图能 OCR,就能转成文字。这条路你堵不住,除非你根本不让用户看见内容——那防复制也就失去意义了。
所以前端防复制要解决的从来都是「拿到要花多大力气」的问题,「能不能拿到」这件事早就有答案了。理解了这一点,下面所有技术你就知道该怎么评价了。
第一档:防君子的 CSS
添加图片注释,不超过 140 字(可选)
user-select: none
最常见的一招。一行 CSS,禁止用户选中文字:
.protected {
user-select: none;
-webkit-user-select: none;
}
选不中,自然就 Ctrl+C 不了。
怎么破?打开 F12,在 Elements 面板里把这条规则删掉,或者在 Console 里敲一句:
document.querySelector('.protected').style.userSelect = 'text';
选中恢复,三秒钟的事。
禁用右键菜单
添加图片注释,不超过 140 字(可选)
document.addEventListener('contextmenu', e => e.preventDefault());
用户右键弹不出「复制」菜单。这招的副作用比效果大——用户想用右键翻译、右键查词都不行了,体验直接劝退。
破解更简单,浏览器设置里有「允许网站阻止右键」的开关,关掉就行。
这两招的共同特点:拦得住完全不懂技术的人,懂一点的人当场破。我在实验室里给它们的「绕过难度」都标了 1,意思是小白十秒钟搞定。
第二档:JavaScript 拦事件
拦截 copy 事件
添加图片注释,不超过 140 字(可选)
document.addEventListener('copy', e => {
e.preventDefault();
});
用户按 Ctrl+C,复制操作被取消,剪贴板里什么都没有。
比 CSS 那招稍微难破一点,但也就稍微。在 Console 里抢先注册一个捕获阶段的监听器,把事件拦截掉就行:
document.addEventListener('copy', e => e.stopImmediatePropagation(), true);
透明遮罩层
在文字上面盖一层透明的 div,用户的鼠标点到的是遮罩,选不中底下的文字:
.overlay {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 999;
}
破法和前面一样,F12 删掉遮罩元素,或者把它的 pointer-events 设成 none。
第三档:换个思路,允许复制但加料
前面那些硬拦的招数,最大的问题是误伤正常用户。有人就想引用你一句话,结果发现连选中都不行,体验极差。
于是主流网站换了个思路:不拦复制,但在你复制的内容里偷偷加东西。
剪贴板内容替换
document.addEventListener('copy', e => {
e.preventDefault();
const text = window.getSelection().toString();
e.clipboardData.setData('text/plain',
text + '\n\n——来源:xxx,禁止转载');
});
你复制一段话,粘贴出来发现后面多了一行来源声明和原文链接。
知乎、CSDN、纽约时报用的都是这套。它的聪明之处在于:不影响正常阅读和引用,但内容被搬走时能留下出处。从「禁止复制」变成「复制可以,但要带上我的署名」,对内容方反而更有价值。
这招也能破——在 DevTools 里选中文字,右键「Store as global variable」直接拿到原始 DOM 文本,绕过 copy 事件。但对绝大多数转载者来说,那行来源声明已经达到目的了。
第四档:让内容根本不是「文本」
到这一档,思路变了:既然 DOM 里的文本能被抠出来,那就别让内容以文本形式存在。
Canvas 渲染
把文字用 fillText 画到 Canvas 画布上:
const ctx = canvas.getContext('2d');
ctx.font = '16px sans-serif';
ctx.fillText('机密内容', 10, 30);
用户看到的是图片,DOM 里没有任何文本节点,常规爬虫抓下来是一片空白。
代价很大:内容不可被搜索引擎索引(SEO 归零)、屏幕阅读器读不了(无障碍崩溃)、渲染性能差。百度文库的付费文档预览走的就是这条路。
破法:截图加 OCR,或者直接 hook Canvas 的 fillText 调用拿到参数。
字体映射混淆
这是前端能做到的最强招数。自定义一个字体文件,把字符的编码映射打乱——代码里写的是一套编码,字体显示出来是另一套字。
@font-face {
font-family: "Obfuscated";
src: url("shuffled.woff2");
}
/* HTML 里写的是 "xk#2m",屏幕上显示的是 "hello" */
页面上看着是正常文字,你复制下来的却是 xk#2m 这样的乱码,因为剪贴板拿到的是真实编码,不是字体渲染后的字形。
起点中文网的付费章节用的就是这招,每个用户、每个章节的字体映射还可能不一样。
破法:分析字体文件的 cmap 表反推出映射关系,或者老办法——OCR。在实验室里我给它的绕过难度标了 5,意思是需要专门的工具和技术,但依然不是不可能。
我把这些招做成了攻防模拟
光说「能破」太抽象,所以我在实验室里加了个攻防模拟:
添加图片注释,不超过 140 字(可选)
你先在实验台上挑几种防护组合起来,然后选一个攻击者水平——从「只会 Ctrl+C 的普通用户」到「带 OCR 和自动化工具的专业爬虫」——看看每道防线能撑多久。
结论很一致:
- 普通用户:CSS 那两招就能把他挡在门外
- 前端开发者:F12 一开,第一、二档全部失效
- 专业爬虫:配合 OCR,连字体混淆和 Canvas 都能拿下,只是慢一点
这正好对应一个朴素的道理:防护真正的价值在于把绕过成本抬高到攻击者觉得不值得,绝对拦住本来就做不到。对付随手复制的访客和对付铁了心要扒站的爬虫,需要的防护强度完全不同。
说明:实验室里每种技术的「绕过耗时」是基于难度等级和攻击者水平用公式模拟出来的相对数值,用来直观对比强弱,不代表真实世界的精确秒数。
参考来源
- MDN Web Docs, “user-select” CSS property developer.mozilla.org/en-US/docs/…
- MDN Web Docs, “Element: copy event” developer.mozilla.org/en-US/docs/…
- MDN Web Docs, “ClipboardEvent.clipboardData” developer.mozilla.org/en-US/docs/…
- W3C, “Clipboard API and events” www.w3.org/TR/clipboar…