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

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

微信或飞书截图都有 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

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

🔗 相关链接

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax