女朋友做广告策划,每天要从海量网站和素材中摘抄文案。
微信或飞书截图都有 OCR,但她总要"切微信/飞书 → 识别 → 复制 → 切回浏览器",来回折腾好麻烦,经常被打断思路。
两个最常见的烦恼:
禁止复制的页面:设计灵感站、素材站、文库类网站,明明能看到文字,就是选不了、右键也没用,只能手敲。
图片里的文字无法快速提取:看到一张图,得切到微信、点"提取文字"、等识别、复制、再切回来粘贴。
一张图,好几个步骤,来回切换三次。
有天晚上她在赶方案,一边操作一边念叨:"太麻烦了,思路都断了......"
我说:"要不我给你写个插件?"
于是周末两天,做了这个 「图文解锁器」:
- 一键解除网页限制 ------ 禁选中、禁右键的网站,点一下就能正常复制
- 浏览器里直接拖框识别 ------ 不用切微信,看到哪里框哪里,几秒出结果
- 所有操作在侧边栏完成 ------ 不遮挡页面,不用来回切窗口
周一她用上之后的评价:"终于顺手了!那些恶心的禁止复制网站现在随便复制,图片识别也不用跳来跳去了。"
下面讲讲开发过程和技术实现 👇
效果演示: 解锁网页禁用复制 + 一键提取图片文字

功能特性
1. 解除网页限制
- 一键解除复制限制
- 恢复右键菜单
- 允许文本选中
- 支持动态加载的网页内容解除限制
2. OCR 文字识别(方式与特性)
| 方式 | 说明 | 使用场景 |
|---|---|---|
| 页面截图 | 快速截取当前可见区域 | 一键识别网页全部内容 |
| 自选区域 | 拖拽选择任意区域 | 只识别你选中的特定区域 |
| 点击上传 | 选择本地图片文件 | 识别本地图片中的文字 |
| 拖拽上传 | 拖入图片即可识别 | 方便上传图片并识别文字 |
- 基于腾讯云 OCR API(每月 1000 次免费额度)
- 内置图片预览,识别前可确认内容
- 识别结果一键复制,支持后续粘贴使用
- 识别流程:选择方式 → 预览 → 开始识别 → 复制/下载
快速开始(安装使用)
1. 获取代码
- GitHub :github.com/Teernage/co... ⭐ 如果对您有帮助,欢迎Star
- Gitee :gitee.com/xuzhenxin11...
2. 安装扩展(本地加载)
- 打开 Chrome,地址栏输入 chrome://extensions/
- 右上角开启"开发者模式"
- 点击"加载已解压的扩展程序"
- 选择插件目录
- 安装完成
小贴士:Side Panel 依赖较新版本 Chrome,建议 114+。

3. 首次使用
- 点击插件图标打开侧边栏
- 点击「一键解除」即可解除页面复制限制
- 使用截图功能(页面截图 / 自选区域)或本地上传(点击上传 / 拖拽上传)进行 OCR 识别
- 首次体验可直接使用以下账号(每月 1000 次免费额度):
- SecretId: AKIDLUQ7aqsjNmwufWFm590d1BxXs0xgBRTH
- SecretKey: c09OVP4aw75oIYZMvFO8j5C5uiIgspIc
- 将 SecretId 和 SecretKey 填入插件侧边栏后保存设置,即可开始图文识别
示例界面:
注:如免费额度用完,请根据下方指导申请属于你自己的账号哦。👇
腾讯云 OCR 开通与配置
插件支持腾讯云 OCR,每月有约 1000 次免费额度,足够日常使用。首次识别前,请按以下步骤开通并配置:
- 进入 腾讯云文字识别控制台console.cloud.tencent.com/ocr/v2/over... ,勾选条款并开通服务。
- 前往 API 密钥管理console.cloud.tencent.com/cam/capi 获取自己的 SecretId 和 SecretKey。
- 将 SecretId 和 SecretKey 填入插件侧边栏并保存设置,即可开始图文识别。
注意事项
-
API 费用:腾讯云 OCR 每个月有1000个请求的免费额度,超出后按量计费
-
权限说明:
activeTab:访问当前标签页scripting:注入脚本storage:保存配置sidePanel:侧边栏功能
-
兼容性:
- Chrome 114+ (Side Panel API 要求)
- 部分网站可能有额外防护
技术实现原理
核心技术栈
- Manifest V3:Chrome 扩展最新规范
- Side Panel API:侧边栏界面
- Content Scripts:页面注入脚本
- Tencent Cloud OCR:腾讯云文字识别 API
- Canvas API:图片裁剪
架构设计
bash
copy-everything
├── manifest.json # 插件配置
├── icons # 插件图标
├── background.js # 后台服务
├── content.js # 内容脚本(核心功能)
├── sidepanel.html # 侧边栏界面
├── sidepanel.js # 侧边栏界面逻辑
├── sidepanel.css # 侧边栏样式
└── tencentOCR.js # OCR SDK
核心代码解析
1. 解除复制限制 (五层防护)
这是插件的核心功能之一,通过多层防护确保解除限制:

实现原理
通过五层防护确保解除限制
第一层:CSS 强制覆盖
js
const style = document.createElement('style');
style.textContent = `
html, body, * {
-webkit-user-select: text !important;
-moz-user-select: text !important;
user-select: text !important;
}
`;
document.head.appendChild(style);
作用:强制允许文本选中,覆盖网站的 CSS 限制。
第二层:事件拦截(捕获阶段)
js
// 在捕获阶段拦截所有限制事件
const restrictedEvents = [
'copy', 'cut', 'paste', 'contextmenu',
'selectstart', 'dragstart', 'keydown', 'keyup', 'keypress'
];
const stopEvent = (e) => {
e.stopPropagation();
e.stopImmediatePropagation?.(); // 阻止其他监听器执行
};
// 在 window、document、documentElement、body 四个层级同时监听
[window, document, document.documentElement, document.body].forEach(target => {
if (target) {
restrictedEvents.forEach(eventType => {
target.addEventListener(eventType, stopEvent, true); // ← 捕获阶段
});
}
});
关键点:
- 在捕获阶段拦截(优先级最高)
- 使用
stopImmediatePropagation()阻止其他监听器 - 在四个层级监听(window → document → html → body)
为什么要用捕获阶段?
markdown
事件流:捕获阶段 → 目标阶段 → 冒泡阶段
↓
我们在这里拦截(优先级最高)
第三层:移除内联事件属性
js
// 移除所有事件属性(如 oncopy="return false")
const eventAttrs = [
'oncopy', 'oncut', 'onpaste', 'oncontextmenu',
'onselectstart', 'onkeydown', 'ondragstart'
];
document.querySelectorAll(eventAttrs.map(a => `[${a}]`).join(','))
.forEach(el => {
eventAttrs.forEach(attr => el.removeAttribute(attr));
});
作用 :移除 HTML 标签上的内联事件(如 oncopy="return false")
第四层:API 劫持(重写 preventDefault)
js
// 注入到页面上下文,重写原生方法
const script = document.createElement('script');
script.textContent = `
(function() {
if (window.__preventDefaultDisabled) return;
window.__preventDefaultDisabled = true;
const blockedEvents = new Set(${JSON.stringify(restrictedEvents)});
const originalPreventDefault = Event.prototype.preventDefault;
Event.prototype.preventDefault = function() {
if (blockedEvents.has(this.type)) return; // ← 禁用 preventDefault
return originalPreventDefault.call(this);
};
})();
`;
document.documentElement.appendChild(script);
script.remove();
为什么要注入 <script> 标签?
- Content Script 运行在隔离环境(Isolated World)
- 无法直接修改页面的
Event.prototype - 通过
<script>标签注入到页面上下文(Main World)
第五层:动态内容监听
js
// 监听 DOM 变化,自动处理动态加载的元素
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes?.forEach((node) => {
if (node instanceof Element) {
// 移除事件属性
eventAttrs.forEach(attr => node.removeAttribute(attr));
// 强制允许选中
node.style?.setProperty('user-select', 'text', 'important');
}
});
});
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
作用:监听 DOM 变化,自动处理动态加载的元素(如 React/Vue 渲染的内容)。
2. 自选区域截图
三步完成截图
- 按下按钮 → 屏幕变暗
- 拖动框 → :按住鼠标拖出一个框,选出你想要的区域
- 松开手 → 自动截取框内内容
实现步骤
这背后发生了什么?
步骤 1:创建选框工具
js
// 创建遮罩层(就像给屏幕盖了一层磨砂玻璃)
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
left: 0; top: 0;
width: 100vw; height: 100vh;
background: rgba(0,0,0,0.5); /* 半透明黑色 */
cursor: crosshair; /* 十字准星 */
z-index: 999999;
`;
document.body.appendChild(overlay);
当你点击"自选区域"后,插件会:
- 在屏幕上盖一层半透明黑色遮罩(让你看清楚要选什么)
- 鼠标变成十字准星(提示你可以开始拖框了)
- 准备好一个绿色边框(等你拖出来就显示)
2. 跟随你的鼠标画框(实时反馈)
js
// 鼠标按下 → 记录起点
function handleMouseDown(e) {
startX = e.clientX; // 记住你点击的 X 坐标
startY = e.clientY; // 记住你点击的 Y 坐标
}
// 鼠标移动 → 实时更新框的大小
function handleMouseMove(e) {
currentX = e.clientX;
currentY = e.clientY;
// 计算框的位置和大小(支持反向拖拽)
const left = Math.min(startX, currentX); // 取最小值作为左边界
const top = Math.min(startY, currentY); // 取最小值作为上边界
const width = Math.abs(currentX - startX); // 宽度 = 绝对值
const height = Math.abs(currentY - startY);// 高度 = 绝对值
// 更新绿色边框的位置
selectionBox.style.left = left + 'px';
selectionBox.style.top = top + 'px';
selectionBox.style.width = width + 'px';
selectionBox.style.height = height + 'px';
}
当你按住鼠标拖动时:
- 记录你按下的位置(起点)
- 记录你当前的位置(终点)
- 用这两个点的横坐标和纵坐标的差值画出一个矩形框
支持反向拖拽:不管你从左上往右下拖,还是从右下往左上拖,都能正确识别!
举个例子:
js
你从 (100, 100) 拖到 (300, 300)
→ 框的位置:left=100, top=100, width=200, height=200 ✅
你从 (300, 300) 拖到 (100, 100)(反向)
→ 框的位置:left=100, top=100, width=200, height=200 ✅
3. 精准裁剪(像剪刀一样裁图)
当你松开鼠标后,插件会:
- 先截取整个页面(就像拍了一张全屏照片)
- 再裁剪出你选中的部分(就像用剪刀剪出你要的区域)
关键技术:Canvas 画布裁剪
js
// 1. 创建一个画布(就像准备一张白纸)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 2. 设置画布大小 = 你选中区域的大小
canvas.width = width;
canvas.height = height;
// 3. 把全屏截图的"选中部分"画到画布上
ctx.drawImage(
fullScreenImage, // 全屏截图
left, top, // 从哪里开始裁剪(你选中区域的左上角)
width, height, // 裁剪多大(你选中区域的宽高)
0, 0, // 画到画布的哪里(从画布左上角开始)
width, height // 画多大(填满整个画布)
);
// 4. 导出成图片
const croppedImage = canvas.toDataURL('image/png');
用生活场景类比:
- 全屏截图 = 拍了一张班级照
- 你选中的区域 = 你只想要照片里的某个人
- Canvas 裁剪 = 用剪刀把那个人剪下来
步骤 4:四遮罩高亮效果
问题:如果只用一个全屏遮罩,选中的区域也会被遮住,用户看不清选了什么。
解决方案:用 4 个遮罩块围绕选区。
┌─────────────────────────────────┐
│ 上遮罩(半透明黑色) │
├──────┬──────────────┬───────────┤
│ 左遮罩│ 选区(高亮) │ 右遮罩 │
├──────┴──────────────┴───────────┤
│ 下遮罩(半透明黑色) │
└─────────────────────────────────┘
代码实现:
js
// 创建 4 个遮罩块
const masks = ['top', 'right', 'bottom', 'left'].map(position => {
const mask = document.createElement('div');
mask.style.cssText = `
position: fixed;
background: rgba(0,0,0,0.5);
pointer-events: none; /* 不阻挡鼠标事件 */
`;
return mask;
});
// 根据选区位置更新遮罩块大小
function updateMasks(left, top, width, height) {
masks[0].style.cssText += `left:0; top:0; width:100vw; height:${top}px;`; // 上
masks[1].style.cssText += `left:${left+width}px; top:${top}px; width:${window.innerWidth-left-width}px; height:${height}px;`; // 右
masks[2].style.cssText += `left:0; top:${top+height}px; width:100vw; height:${window.innerHeight-top-height}px;`; // 下
masks[3].style.cssText += `left:0; top:${top}px; width:${left}px; height:${height}px;`; // 左
}
高分屏适配(让图片更清晰)
问题:Retina 屏幕(如 MacBook)的像素密度是普通屏幕的 2 倍,如果不处理会导致截图模糊。
解决方案:乘以设备像素比
js
const scale = window.devicePixelRatio || 1; // Retina 屏幕 = 2,普通屏幕 = 1
// 设置画布大小时要乘以 scale
canvas.width = width * scale;
canvas.height = height * scale;
// 裁剪时也要乘以 scale
ctx.drawImage(
fullScreenImage,
left * scale, top * scale, // ← 乘以 scale
width * scale, height * scale, // ← 乘以 scale
0, 0,
width * scale, height * scale
);
完整流程

时序图
3. 本地上传图片
方式一:点击上传
HTML:
js
<input type="file" id="uploadInput" accept="image/*" style="display:none">
<button onclick="document.getElementById('uploadInput').click()">
📁 上传图片
</button>
JavaScript:
js
document.getElementById('uploadInput').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (event) => {
const base64 = event.target.result.split(',')[1];
await recognizeText(base64);
};
reader.readAsDataURL(file);
});
方式二:拖拽上传
js
const dropZone = document.getElementById('dropZone');
// 1. 阻止默认行为
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
});
});
// 2. 视觉反馈
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.add('drag-over');
});
});
// 3. 处理文件
dropZone.addEventListener('drop', (e) => {
const file = e.dataTransfer.files[0];
if (!file.type.startsWith('image/')) {
alert('请上传图片文件!');
return;
}
const reader = new FileReader();
reader.onload = async (event) => {
const base64 = event.target.result.split(',')[1];
await recognizeText(base64);
};
reader.readAsDataURL(file);
});
CSS:
css
#dropZone {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.3s;
}
#dropZone.drag-over {
border-color: #4CAF50;
background: rgba(76, 175, 80, 0.1);
}
- 作用:把图片拖进上传图片区域 ,拦住浏览器默认行为,在 drop 时读文件并识别或预览。
4.页面截图
js
const capturePageBtn = document.getElementById('capturePageBtn');
capturePageBtn.addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {
format: 'png'
});
const base64 = dataUrl.split(',')[1];
await recognizeText(base64);
});
直接使用chrome插件的截图功能 权限配置(manifest.json):
json
{ "permissions": ["activeTab", "tabs"] }
5. OCR 识别
使用腾讯云 OCR API,通过 TC3-HMAC-SHA256 签名调用 GeneralBasicOCR 接口,每月 1000 次免费额度。
js
// 核心调用代码
const ocr = new TencentOCR(secretId, secretKey);
const text = await ocr.recognizeText(imageBase64);
总结
这个插件解决了日常浏览网页时的两大痛点:
- 解除限制:让你自由复制任何内容
- OCR 识别:快速提取图片文字
技术亮点
| 技术点 | 难点 | 解决方案 |
|---|---|---|
| 解除限制 | 网站多层防护 | 五层拦截 + API 劫持 |
| 自选截图 | 高分屏模糊 | devicePixelRatio 适配 |
| 拖拽上传 | 视觉反馈 | CSS 过渡动画 |
| OCR 识别 | API 签名 | TC3-HMAC-SHA256 |
希望这个插件能帮到大家!如果有问题或建议,欢迎在评论区交流~