# 在浏览器里随手识别二维码:一个 Tampermonkey 用户脚本的实现记录  二维码已经无处不在:文章里的活动海报、聊天截图、登录页上的设备码、下载页面中的跳转码……真正麻烦的是,二维码经常只以一张图片的形式出现。保存图片、打开手机、再手动输入内容,步骤多且容易打断当前工作流。 于是我做了一个浏览器用户脚本:**网页二维码识别器**。安装 Tampermonkey 或 Violentmonkey 后,它可以直接在当前网页中寻找二维码,也可以对单张图片或本地文件进行识别。 ## 它解决了什么问题 - 页面打开后自动扫描图片,识别结果集中显示在右侧面板。 - 对任意网页图片右键,选择“识别这张图片”即可单独处理。 - 支持点击上传和拖拽上传,适合聊天截图、海报和下载后的原图。 - 结果使用 `GM_setValue` 持久化,刷新页面后仍然保留。 - 识别结果会区分“链接”和“文本”:链接可以直接新标签页打开,所有内容都可以一键复制。 - 页面图片来源的结果支持“定位图片”,脚本会滚动到原图并短暂高亮。 ## 核心实现:把图片交给 Canvas 和 jsQR 脚本通过 `@require` 引入 `jsQR@1.4.0`。识别流程集中在几个小函数中: 1. 读取 `` 的自然尺寸,过滤掉过小或未加载完成的图片。 2. 将图片绘制到 Canvas,并尝试多个目标边长(最高约 `1440px`),避免原图过大或过小影响识别。 3. 先用原始像素调用 `jsQR`,再生成一次灰度二值图重试。 4. 对每种图像数据分别尝试 `attemptBoth`、`dontInvert` 和 `onlyInvert` 三种反色策略。 对应的代码大致如下: ```js const original = context.getImageData(0, 0, canvas.width, canvas.height); return tryDecodeImageData(original, canvas.width, canvas.height) || tryDecodeImageData(createBinaryImageData(original), canvas.width, canvas.height); ``` 这套策略不依赖服务端接口,图片像素在浏览器本地处理,识别结果也只保存在用户脚本自己的存储空间里。 ## 页面自动扫描为什么不会卡住 页面扫描最多取前 `120` 张已加载图片,并以每批 `4` 张的方式并行识别。每张图片的地址会记录在 `scannedSources` 集合中,避免同一来源被重复处理。 动态网站里的图片并不一定在首屏时就存在,所以脚本还注册了 `MutationObserver`:检测到新增节点或图片 `src` 变化后,延迟约 `800ms` 再发起一次扫描。这个小小的延迟可以把连续的 DOM 更新合并起来,减少重复工作。 ## 三种入口,覆盖不同场景 ### 1. 自动扫描当前页面 脚本在 `document-end` 阶段初始化,约 `1.2s` 后开始扫描当前页面。右下角的悬浮按钮会显示已保存结果数量,点击即可展开侧栏。 ### 2. 右键识别单张图片 脚本监听 `contextmenu` 事件,并在目标元素是 `` 时显示自己的菜单。菜单提供“识别这张图片”和“在新标签打开原图”两个动作。 如果还想使用网站原本的右键菜单,按住 `Shift` 再右键即可跳过脚本菜单。 ### 3. 上传或拖拽本地图片 侧栏中提供隐藏的 `input[type="file"]` 和拖拽区域。上传文件会通过 `URL.createObjectURL` 生成临时地址,识别完成后立即 `revokeObjectURL`,不会长期占用浏览器资源。 ## 结果数据如何管理 每条结果包含二维码内容、来源、来源类型、时间和当前页面地址。脚本用“来源 + 内容”作为去重依据,再按创建时间倒序展示: ```js const existing = new Map( results.map((item) => [`${item.origin}|${item.source}|${item.data}`, item]) ); ``` 这让同一张图片重复扫描时不会产生一堆重复卡片。对于链接结果,界面额外提供“打开链接”;对于网页图片来源,则提供“定位图片”。 ## 安装与使用 1. 安装 [Tampermonkey](https://www.tampermonkey.net/) 或 Violentmonkey。 2. 新建用户脚本,将 [`qrcode-scanner.user.js`](./qrcode-scanner.user.js) 的内容粘贴进去并保存。 3. 刷新任意网页,点击右下角按钮打开识别器。 4. 需要时使用“重新扫描”、上传/拖拽图片,或在图片上右键识别。 脚本匹配所有 HTTP/HTTPS 页面,并依赖 jsDelivr 加载 `jsQR`: ```js // @match *://*/* // @require https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js ``` ## 识别失败时怎么排查 二维码完整、清晰、尺寸足够大时成功率最高。遇到失败,可以按下面的顺序尝试: - 打开图片原图,避免缩略图或二次压缩。 - 确保二维码没有被裁切、遮挡或严重透视变形。 - 先右键“在新标签打开原图”,再使用“上传识别”。 - 动态页面没有及时识别时,点击一次“重新扫描”。 需要注意的是,部分站点会限制跨域图片读取。此时网页内自动扫描可能拿不到像素数据,但下载原图后走本地上传入口通常更稳定。 ## 小结 这个脚本没有引入后端服务,也没有试图替代专业的批量识别工具。它的定位很简单:把“看到二维码”到“拿到二维码内容”的路径缩短到一次点击,并且把网页、右键、本地上传三个入口统一到同一个结果面板里。 完整代码见 [`qrcode-scanner.user.js`](./qrcode-scanner.user.js),欢迎根据自己的工作流继续扩展,例如增加导出 JSON、识别历史搜索或更细粒度的站点白名单。 Loading... # 在浏览器里随手识别二维码:一个 Tampermonkey 用户脚本的实现记录  二维码已经无处不在:文章里的活动海报、聊天截图、登录页上的设备码、下载页面中的跳转码……真正麻烦的是,二维码经常只以一张图片的形式出现。保存图片、打开手机、再手动输入内容,步骤多且容易打断当前工作流。 于是我做了一个浏览器用户脚本:**网页二维码识别器**。安装 Tampermonkey 或 Violentmonkey 后,它可以直接在当前网页中寻找二维码,也可以对单张图片或本地文件进行识别。 ## 它解决了什么问题 - 页面打开后自动扫描图片,识别结果集中显示在右侧面板。 - 对任意网页图片右键,选择“识别这张图片”即可单独处理。 - 支持点击上传和拖拽上传,适合聊天截图、海报和下载后的原图。 - 结果使用 `GM_setValue` 持久化,刷新页面后仍然保留。 - 识别结果会区分“链接”和“文本”:链接可以直接新标签页打开,所有内容都可以一键复制。 - 页面图片来源的结果支持“定位图片”,脚本会滚动到原图并短暂高亮。 ## 核心实现:把图片交给 Canvas 和 jsQR 脚本通过 `@require` 引入 `jsQR@1.4.0`。识别流程集中在几个小函数中: 1. 读取 `<img>` 的自然尺寸,过滤掉过小或未加载完成的图片。 2. 将图片绘制到 Canvas,并尝试多个目标边长(最高约 `1440px`),避免原图过大或过小影响识别。 3. 先用原始像素调用 `jsQR`,再生成一次灰度二值图重试。 4. 对每种图像数据分别尝试 `attemptBoth`、`dontInvert` 和 `onlyInvert` 三种反色策略。 对应的代码大致如下: ```js const original = context.getImageData(0, 0, canvas.width, canvas.height); return tryDecodeImageData(original, canvas.width, canvas.height) || tryDecodeImageData(createBinaryImageData(original), canvas.width, canvas.height); ``` 这套策略不依赖服务端接口,图片像素在浏览器本地处理,识别结果也只保存在用户脚本自己的存储空间里。 ## 页面自动扫描为什么不会卡住 页面扫描最多取前 `120` 张已加载图片,并以每批 `4` 张的方式并行识别。每张图片的地址会记录在 `scannedSources` 集合中,避免同一来源被重复处理。 动态网站里的图片并不一定在首屏时就存在,所以脚本还注册了 `MutationObserver`:检测到新增节点或图片 `src` 变化后,延迟约 `800ms` 再发起一次扫描。这个小小的延迟可以把连续的 DOM 更新合并起来,减少重复工作。 ## 三种入口,覆盖不同场景 ### 1. 自动扫描当前页面 脚本在 `document-end` 阶段初始化,约 `1.2s` 后开始扫描当前页面。右下角的悬浮按钮会显示已保存结果数量,点击即可展开侧栏。 ### 2. 右键识别单张图片 脚本监听 `contextmenu` 事件,并在目标元素是 `<img>` 时显示自己的菜单。菜单提供“识别这张图片”和“在新标签打开原图”两个动作。 如果还想使用网站原本的右键菜单,按住 `Shift` 再右键即可跳过脚本菜单。 ### 3. 上传或拖拽本地图片 侧栏中提供隐藏的 `input[type="file"]` 和拖拽区域。上传文件会通过 `URL.createObjectURL` 生成临时地址,识别完成后立即 `revokeObjectURL`,不会长期占用浏览器资源。 ## 结果数据如何管理 每条结果包含二维码内容、来源、来源类型、时间和当前页面地址。脚本用“来源 + 内容”作为去重依据,再按创建时间倒序展示: ```js const existing = new Map( results.map((item) => [`${item.origin}|${item.source}|${item.data}`, item]) ); ``` 这让同一张图片重复扫描时不会产生一堆重复卡片。对于链接结果,界面额外提供“打开链接”;对于网页图片来源,则提供“定位图片”。 ## 安装与使用 1. 安装 [Tampermonkey](https://www.tampermonkey.net/) 或 Violentmonkey。 2. 新建用户脚本,将 [`qrcode-scanner.user.js`](./qrcode-scanner.user.js) 的内容粘贴进去并保存。 3. 刷新任意网页,点击右下角按钮打开识别器。 4. 需要时使用“重新扫描”、上传/拖拽图片,或在图片上右键识别。 脚本匹配所有 HTTP/HTTPS 页面,并依赖 jsDelivr 加载 `jsQR`: ```js // @match *://*/* // @require https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js ``` ## 识别失败时怎么排查 二维码完整、清晰、尺寸足够大时成功率最高。遇到失败,可以按下面的顺序尝试: - 打开图片原图,避免缩略图或二次压缩。 - 确保二维码没有被裁切、遮挡或严重透视变形。 - 先右键“在新标签打开原图”,再使用“上传识别”。 - 动态页面没有及时识别时,点击一次“重新扫描”。 需要注意的是,部分站点会限制跨域图片读取。此时网页内自动扫描可能拿不到像素数据,但下载原图后走本地上传入口通常更稳定。 ## 小结 这个脚本没有引入后端服务,也没有试图替代专业的批量识别工具。它的定位很简单:把“看到二维码”到“拿到二维码内容”的路径缩短到一次点击,并且把网页、右键、本地上传三个入口统一到同一个结果面板里。 完整代码见 [`qrcode-scanner.user.js`](./qrcode-scanner.user.js),欢迎根据自己的工作流继续扩展,例如增加导出 JSON、识别历史搜索或更细粒度的站点白名单。 © 允许规范转载