简介:本文以资料摘抄中的高频痛点为切入点,系统讲解如何开发一个浏览器扩展,实现解除网页复制限制与图片 OCR 文字提取两大能力。内容涵盖事件拦截、CSS 覆盖、动态页面持续解锁、可见区域截图与选区裁剪、云端文字识别服务接入等关键环节,并附可落地的代码示例与工程化建议,适合希望提升浏览器扩展开发能力的开发者阅读。
日常整理资料、摘抄文案时,有两类场景尤其令人烦躁:
把这两个痛点翻译成技术问题,就是:
本文围绕这两个问题,给出一套可落地的扩展开发方案。
推荐采用 Manifest V3 规范的浏览器扩展结构,核心模块与职责划分如下:
数据流可以概括为:用户在侧边栏选择取图方式 → 后台截图或内容脚本裁剪选区 → 图片以 Base64 形式交给后台 → 后台调用云端 OCR → 结果回传侧边栏渲染 → 一键复制。
这种“页面只管采集、后台只管请求、侧边栏只管展示”的分层设计,让每一环都可以独立调试与替换。
要“对症下药”,先了解常见的限制方式:
copy、cut、paste、selectstart、contextmenu、dragstart 等事件上注册监听并阻止默认行为,这是最普遍的做法;user-select: none 等 CSS 规则禁止文本选中;针对前两类限制,内容脚本里做两件事:在捕获阶段拦截事件、注入覆盖样式。
// 1. 捕获阶段拦截:业务代码注册的监听将收不到这些事件const EVENTS = ['copy', 'cut', 'paste', 'selectstart', 'contextmenu', 'dragstart'];EVENTS.forEach(type => {document.addEventListener(type, e => e.stopPropagation(), true);});// 2. 注入样式:恢复文本可选const style = document.createElement('style');style.textContent = `*, *::before, *::after {user-select: text !important;-webkit-user-select: text !important;}`;document.documentElement.appendChild(style);
捕获阶段监听会先于页面业务代码执行,stopPropagation 之后,注册在冒泡阶段的禁用逻辑便无从触发。对于通过元素 oncopy 属性或 addEventListener 动态绑定的情况,还可以重写原型进行过滤:
// 3. 拦截后续注册的禁用逻辑const rawAdd = EventTarget.prototype.addEventListener;EventTarget.prototype.addEventListener = function (type, listener, options) {if (EVENTS.includes(type)) return; // 直接忽略return rawAdd.call(this, type, listener, options);};
此外保留一个兜底手段:若某些页面仍然复制失败,可直接读取当前选区文本 window.getSelection().toString(),或从目标节点提取 innerText 写入剪贴板,完全绕过事件链路。
现代页面大量采用前端框架动态渲染,内容会随滚动、路由切换不断更新,一次性注入的样式和拦截可能被新节点“冲掉”。解决办法是通过 MutationObserver 监听 DOM 变化,持续重新应用解锁逻辑:
const applyUnlock = () => { /* 重新注入样式、绑定拦截 */ };const observer = new MutationObserver(applyUnlock);observer.observe(document.documentElement, { childList: true, subtree: true });
对于单页应用的路由切换,可再监听 history 的 pushState 与 popstate 事件,在路由变化后延迟重放一次解锁逻辑,确保新页面同样生效。
实际使用中图片来源通常有三种,方案上要统一收口:
captureVisibleTab 接口,拿到当前标签页可见区域的 Base64 图像,适合整页内容识别;FileReader 读取为 DataURL,适合识别已下载的素材图。三种方式最终都归一为一张 Base64 图片交给后台,后续流程完全复用。
区域选择的核心是一个覆盖全页的遮罩层,记录 mousedown 与 mouseup 的坐标:
const rect = await new Promise(resolve => {const mask = document.createElement('div');Object.assign(mask.style, {position: 'fixed', inset: 0, zIndex: 2147483647,cursor: 'crosshair', background: 'rgba(0,0,0,.3)'});let start = null;mask.addEventListener('mousedown', e => { start = [e.clientX, e.clientY]; });mask.addEventListener('mouseup', e => {const box = {x: Math.min(start[0], e.clientX), y: Math.min(start[1], e.clientY),w: Math.abs(e.clientX - start[0]), h: Math.abs(e.clientY - start[1])};mask.remove();resolve(box);});document.documentElement.appendChild(mask);});
拿到截图后要注意设备像素比换算——高分屏上截图的实际分辨率是 CSS 像素乘以 DPR,若不换算,裁剪区域会整体偏移:
const img = new Image();img.src = dataUrl;await img.decode();const dpr = window.devicePixelRatio || 1;const canvas = document.createElement('canvas');canvas.width = rect.w * dpr;canvas.height = rect.h * dpr;canvas.getContext('2d').drawImage(img,rect.x * dpr, rect.y * dpr, rect.w * dpr, rect.h * dpr,0, 0, canvas.width, canvas.height);const cropped = canvas.toDataURL('image/png');
OCR 能力建议直接复用云厂商的文字识别服务,例如百度智能云的通用文字识别:开通服务后,在后台脚本中先通过密钥换取访问令牌,再携带令牌调用识别接口即可。多数云厂商都提供一定的免费调用额度,个人日常使用基本够用;密钥应保存在扩展的配置存储中,切勿硬编码到代码仓库。
为什么请求要放在后台而不是页面侧?一是规避跨域限制,二是密钥不暴露给页面环境,三是便于统一做限流、重试与日志:
// background service worker:统一转发 OCR 请求async function recognize(base64Image) {const token = await getAccessToken(); // 从配置存储读取密钥并换取令牌const res = await fetch(`${OCR_ENDPOINT}?access_token=${token}`, {method: 'POST',headers: { 'Content-Type': 'application/x-www-form-urlencoded' },body: new URLSearchParams({ image: base64Image })});const data = await res.json();// 按所接服务约定的返回结构解析文本行,再按行拼接return (data.words_result || []).map(item => item.words).join('\n');}
侧边栏承担全部交互:识别前显示图片预览,供用户确认内容无误;识别中给出加载状态;完成后按行渲染文本,并提供一键复制:
await navigator.clipboard.writeText(resultText);
错误处理至少覆盖三类情况:网络失败(提示重试)、调用额度不足(提示检查配额)、图片质量过差(提示重新截图或选择更清晰区域)。识别结果也可顺手支持下载为文本文件,方便归档。
activeTab、scripting、storage 等权限,避免大范围主机权限带来的审核与信任问题;回到最初的诉求:把“看到就想复制”这件事的步骤压到最少。解除复制限制解决“能不能复制”,OCR 链路解决“图里也能复制”,侧边栏则保证整个过程不离开当前页面。三件事合起来,原本三四步的往返操作被压缩成两次点击。
在此基础上还有不少可延伸的方向:接入 PDF 解析与长图分段识别、增加批量处理队列、利用端侧推理(如通过 WebAssembly 运行轻量识别模型)实现离线 OCR、在识别结果上叠加翻译与格式保留等。扩展开发的门槛并不高,难点在于把散落的能力组织成一条顺畅的链路——这也正是这类小工具的价值所在。