女朋友又给我出难题了:解锁网页禁用复制 + 一键提取图片文字

女朋友做广告策划,每天要从海量网站和素材中摘抄文案。

微信或飞书截图都有 OCR,但她总要"切微信/飞书 → 识别 → 复制 → 切回浏览器",来回折腾好麻烦,经常被打断思路。

两个最常见的烦恼

  • 禁止复制的页面:设计灵感站、素材站、文库类网站,明明能看到文字,就是选不了、右键也没用,只能手敲。

  • 图片里的文字无法快速提取:看到一张图,得切到微信、点"提取文字"、等识别、复制、再切回来粘贴。

一张图,好几个步骤,来回切换三次。

有天晚上她在赶方案,一边操作一边念叨:"太麻烦了,思路都断了......"

我说:"要不我给你写个插件?"

于是周末两天,做了这个 「图文解锁器」

  1. 一键解除网页限制 ------ 禁选中、禁右键的网站,点一下就能正常复制
  2. 浏览器里直接拖框识别 ------ 不用切微信,看到哪里框哪里,几秒出结果
  3. 所有操作在侧边栏完成 ------ 不遮挡页面,不用来回切窗口

周一她用上之后的评价:"终于顺手了!那些恶心的禁止复制网站现在随便复制,图片识别也不用跳来跳去了。"

下面讲讲开发过程和技术实现 👇

效果演示: 解锁网页禁用复制 + 一键提取图片文字

功能特性

1. 解除网页限制

  • 一键解除复制限制
  • 恢复右键菜单
  • 允许文本选中
  • 支持动态加载的网页内容解除限制

2. OCR 文字识别(方式与特性)

方式 说明 使用场景
页面截图 快速截取当前可见区域 一键识别网页全部内容
自选区域 拖拽选择任意区域 只识别你选中的特定区域
点击上传 选择本地图片文件 识别本地图片中的文字
拖拽上传 拖入图片即可识别 方便上传图片并识别文字
  • 基于腾讯云 OCR API(每月 1000 次免费额度)
  • 内置图片预览,识别前可确认内容
  • 识别结果一键复制,支持后续粘贴使用
  • 识别流程:选择方式 → 预览 → 开始识别 → 复制/下载

快速开始(安装使用)

1. 获取代码

2. 安装扩展(本地加载)

  1. 打开 Chrome,地址栏输入 chrome://extensions/
  2. 右上角开启"开发者模式"
  3. 点击"加载已解压的扩展程序"
  4. 选择插件目录
  5. 安装完成

小贴士:Side Panel 依赖较新版本 Chrome,建议 114+。

3. 首次使用

  • 点击插件图标打开侧边栏
  • 点击「一键解除」即可解除页面复制限制
  • 使用截图功能(页面截图 / 自选区域)或本地上传(点击上传 / 拖拽上传)进行 OCR 识别
  • 首次体验可直接使用以下账号(每月 1000 次免费额度):
    • SecretId: AKIDLUQ7aqsjNmwufWFm590d1BxXs0xgBRTH
    • SecretKey: c09OVP4aw75oIYZMvFO8j5C5uiIgspIc
    • 将 SecretId 和 SecretKey 填入插件侧边栏后保存设置,即可开始图文识别

示例界面:

注:如免费额度用完,请根据下方指导申请属于你自己的账号哦。👇

腾讯云 OCR 开通与配置

插件支持腾讯云 OCR,每月有约 1000 次免费额度,足够日常使用。首次识别前,请按以下步骤开通并配置:

  1. 进入 腾讯云文字识别控制台console.cloud.tencent.com/ocr/v2/over... ,勾选条款并开通服务。
  2. 前往 API 密钥管理console.cloud.tencent.com/cam/capi 获取自己的 SecretId 和 SecretKey。
  3. 将 SecretId 和 SecretKey 填入插件侧边栏并保存设置,即可开始图文识别。

注意事项

  1. API 费用:腾讯云 OCR 每个月有1000个请求的免费额度,超出后按量计费

  2. 权限说明

    • activeTab:访问当前标签页
    • scripting:注入脚本
    • storage:保存配置
    • sidePanel:侧边栏功能
  3. 兼容性

    • 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. 按下按钮 → 屏幕变暗
  2. 拖动框 → :按住鼠标拖出一个框,选出你想要的区域
  3. 松开手 → 自动截取框内内容

实现步骤

这背后发生了什么?

步骤 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. 精准裁剪(像剪刀一样裁图)

当你松开鼠标后,插件会:

  1. 先截取整个页面(就像拍了一张全屏照片)
  2. 再裁剪出你选中的部分(就像用剪刀剪出你要的区域)

关键技术: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
);

完整流程

时序图

sequenceDiagram participant SP as Sidepanel participant CT as Content Script participant BG as Background SP->>CT: startAreaCapture CT->>CT: createCaptureUI + 事件绑定 CT->>CT: 用户拖拽/更新选区 CT->>CT: mouseup → captureSelectedArea CT->>BG: captureVisible BG-->>CT: DataURL(整页可见区域) CT->>CT: Canvas 裁剪(考虑 devicePixelRatio) CT->>SP: areaCaptureDone(DataURL) SP->>SP: 预览 + OCR 识别

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);

总结

这个插件解决了日常浏览网页时的两大痛点:

  1. 解除限制:让你自由复制任何内容
  2. OCR 识别:快速提取图片文字

技术亮点

技术点 难点 解决方案
解除限制 网站多层防护 五层拦截 + API 劫持
自选截图 高分屏模糊 devicePixelRatio 适配
拖拽上传 视觉反馈 CSS 过渡动画
OCR 识别 API 签名 TC3-HMAC-SHA256

希望这个插件能帮到大家!如果有问题或建议,欢迎在评论区交流~

🔗 相关链接

相关推荐
~无忧花开~4 小时前
JavaScript实现PDF本地预览技巧
开发语言·前端·javascript
小时前端5 小时前
“能说说事件循环吗?”—— 我从候选人回答中看到的浏览器与Node.js核心差异
前端·面试·浏览器
IT_陈寒5 小时前
Vite 5.0实战:10个你可能不知道的性能优化技巧与插件生态深度解析
前端·人工智能·后端
SAP庖丁解码5 小时前
【SAP Web Dispatcher负载均衡】
运维·前端·负载均衡
天蓝色的鱼鱼5 小时前
Ant Design 6.0 正式发布:前端开发者的福音与革新
前端·react.js·ant design
HIT_Weston5 小时前
38、【Ubuntu】【远程开发】拉出内网 Web 服务:构建静态网页(一)
linux·前端·ubuntu
零一科技6 小时前
Vue3拓展:自定义权限指令
前端·vue.js
t***D2646 小时前
Vue虚拟现实开发
javascript·vue.js·vr
im_AMBER6 小时前
AI井字棋项目开发笔记
前端·笔记·学习·算法
小时前端6 小时前
Vuex 响应式原理剖析:构建可靠的前端状态管理
前端·面试·vuex