大家好,我是大华!在我们写前端的时候,有时候会遇到这种禁止用户复杂网页内容的需求,这里来分享几种常见的方法,但是这些方法也不能完全阻止内容被复制。
因为用户还是可以通过开发者工具,截图提取文字等等这些方式来进行复制。不过我们也可以在一定程度上提升复制的门槛,防止普通用户随意复制。
以下是几种常用方案:
1. 禁用文本选择
使用 CSS 禁止用户选中文本:
css
body {
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
user-select: none; /* 标准语法 */
}
也可以针对特定元素设置:
html
<div style="user-select: none;">这段文字无法被选中</div>
用户仍可查看源代码或使用其他手段复制。
2. 监听并阻止复制、剪切、粘贴事件
通过 JavaScript 拦截相关键盘和剪贴板事件:
javascript
document.addEventListener('copy', (e) => {
e.preventDefault();
alert('复制已被禁止');
});
document.addEventListener('cut', (e) => {
e.preventDefault();
alert('剪切已被禁止');
});
document.addEventListener('paste', (e) => {
e.preventDefault();
alert('粘贴已被禁止');
});
用户还是可以按下F12禁用 JavaScript 或绕过事件监听。
3. 将内容嵌入图片或 Canvas
将关键内容渲染为图片或使用 <canvas> 绘制,使文本不可直接选中:
html
<img src="text-as-image.png" alt="不可复制的文字">
或使用 canvas 动态绘制:
html
<canvas id="myCanvas"></canvas>
<script>
const ctx = document.getElementById('myCanvas').getContext('2d');
ctx.font = '20px Arial';
ctx.fillText('这段文字无法复制', 10, 50);
</script>
不利于 SEO、无障碍访问(屏幕阅读器无法读取),且加载慢。
4.用户按F12就出发debug功能
当用户按下F12时,触发debug调试,让用户无法选中页面内容。
js
<script>
(function antiDebug() {
let devOpen = false;
const threshold = 100;
function check() {
const start = performance.now();
debugger;
const end = performance.now();
if (end - start > threshold) {
if (!devOpen) {
devOpen = true;
document.body.innerHTML = '<h2>检测到开发者工具,请关闭后重试。</h2>';
// 可选:上报用户行为
// fetch('/log-devtools', { method: 'POST' });
}
} else {
devOpen = false;
}
setTimeout(check, 1000);
}
check();
})();
</script>
5. 动态文本拆分与重组
将文本内容拆分成多个DOM节点,增加复制难度:
html
<div id="protected-text">
<!-- 文本将被JavaScript拆分并插入 -->
</div>
<script>
const text = "这是一段需要保护的机密内容,不能被轻易复制";
const container = document.getElementById('protected-text');
// 将每个字符用span包裹
text.split('').forEach(char => {
const span = document.createElement('span');
span.textContent = char;
span.style.display = 'inline-block'; // 增加选择难度
container.appendChild(span);
});
</script>
进阶版:随机插入不可见字符或零宽字符:
javascript
function obfuscateText(text) {
const zeroWidthSpace = '\u200B'; // 零宽空格
return text.split('').map(char =>
char + zeroWidthSpace.repeat(Math.floor(Math.random() * 3))
).join('');
}
const originalText = "保护内容";
document.getElementById('text').textContent = obfuscateText(originalText);
6. 使用CSS伪元素显示内容
通过CSS的::before或::after伪元素显示文本:
html
<style>
.protected-content::before {
content: "这段文字通过CSS生成,无法直接选中和复制";
}
</style>
<div class="protected-content"></div>
7. 字体反爬虫技术
使用自定义字体文件,将字符映射关系打乱:
css
@font-face {
font-family: 'CustomFont';
src: url('custom-font.woff2') format('woff2');
}
.protected-text {
font-family: 'CustomFont';
}
在字体文件中,将实际字符与显示字符的映射关系打乱,比如:
- 字符
a在字体中实际显示为b - 字符
b显示为c,以此类推
后端配合:服务器动态生成字体文件,定期更换映射关系。
8. Canvas + WebGL 渲染文本
使用更复杂的图形渲染技术:
html
<canvas id="textCanvas" width="800" height="100"></canvas>
<script>
const canvas = document.getElementById('textCanvas');
const ctx = canvas.getContext('2d');
// 设置文字样式
ctx.font = '24px Arial';
ctx.fillStyle = '#333';
// 绘制干扰元素
ctx.fillText('保护内容', 50, 50);
// 添加噪声干扰
for(let i = 0; i < 100; i++) {
ctx.fillRect(
Math.random() * 800,
Math.random() * 100,
1, 1
);
}
</script>
9. SVG 文本渲染
使用SVG渲染文本,增加选择难度:
html
<svg width="400" height="100">
<text x="10" y="30" font-family="Arial" font-size="20"
fill="black" style="user-select: none;">
这段SVG文本难以复制
</text>
<!-- 添加干扰路径 -->
<path d="M10,40 L390,40" stroke="#eee" stroke-width="1"/>
</svg>
10. 实时DOM监控与修复
监控DOM变化,防止用户通过开发者工具修改内容:
javascript
const contentElement = document.getElementById('protected-content');
const originalContent = contentElement.innerHTML;
// 定时检查内容完整性
setInterval(() => {
if (contentElement.innerHTML !== originalContent) {
contentElement.innerHTML = originalContent;
console.log('检测到内容篡改,已恢复');
}
}, 500);
// 使用MutationObserver更精确的监控
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' || mutation.type === 'characterData') {
contentElement.innerHTML = originalContent;
}
});
});
observer.observe(contentElement, {
childList: true,
characterData: true,
subtree: true
});
综合防御策略建议
对于高安全要求的场景,建议采用分层防御:
javascript
class ContentProtector {
constructor() {
this.init();
}
init() {
this.disableSelection();
this.bindEvents();
this.startMonitoring();
this.obfuscateContent();
}
disableSelection() {
document.head.insertAdjacentHTML('beforeend', `
<style>
body { -webkit-user-select: none; user-select: none; }
.protected { cursor: default; }
</style>
`);
}
bindEvents() {
// 绑定所有阻止事件
['copy', 'cut', 'paste', 'contextmenu', 'keydown'].forEach(event => {
document.addEventListener(event, this.preventDefault);
});
}
preventDefault(e) {
e.preventDefault();
return false;
}
startMonitoring() {
// 启动各种监控
this.monitorDevTools();
this.monitorDOMChanges();
}
obfuscateContent() {
// 内容混淆处理
// ...
}
monitorDevTools() {
// 开发者工具检测
// ...
}
monitorDOMChanges() {
// DOM变化监控
// ...
}
}
// 初始化保护
new ContentProtector();
总结
这些方法只能防普通用户,防不住真正想复制的人。
因为别人还是可以通过看源码、截图、关掉 JS 等方式绕过限制。
所以建议:
- 别过度防护,以免影响正常用户和 SEO;
- 重要内容靠后端控制(比如登录才能看全文);
- 组合使用几种方法,提高门槛就好,别追求绝对安全。
毕竟前端展示的内容,就默认是能被看到的,也就能被复制的。
本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《async/await 到底要不要加 try-catch?异步错误处理最佳实践》
《都在用 Java8 和 Java17,那 Java9 到 16 呢?他们真的没用吗?》