平时要截个图或者录段操作演示,常规操作是:截图工具一个软件、录屏软件另一个、标注再换一个。装了三个东西,每个都只用到一小部分。

nForce Screen Capture 是把这几件事合到一个 Chrome 扩展里。纯原生实现,没有构建步骤也没有第三方依赖 —— 克隆下来直接以「未打包扩展」加载就能用。

截图弹窗
弹窗上半是四种截图模式入口,下半是输出格式、延时、是否打开编辑器这些高频开关,最下面挂着最近一次截图和「编辑 / 下载 / 复制」

五种截图模式

模式说明默认快捷键
可视区域截当前视口Alt+Shift+S
整页长截图自动逐屏滚动、捕获并拼接整页Alt+Shift+F
滚动截屏页面出现工具条,你手动滚动,停顿自动捕获,点「完成拼接」输出Alt+Shift+C
框选区域拖拽框选,按设备像素比精确裁剪Alt+Shift+R
录制屏幕见下一节无默认键

除了快捷键,页面上右键也能选这几种模式。

自动长截图和手动滚动为什么都要有

这两个不是重复功能。

整页长截图适合普通页面:脚本自己往下滚,每屏截一次,最后拼起来。快,但遇到懒加载的图片就会截出空白 —— 因为滚动太快,图片还没加载完。

滚动截屏就是给这种情况准备的:页面上浮出一条工具条,你自己滚,滚到哪停一下它就截一帧。图片该加载的加载完了,你满意了再点「完成拼接」。无限滚动页面、瀑布流、动态加载的列表都靠这个。

长截图工具条

拼接做的事情

  • 无缝:按「实际滚动位置 × 缩放比」定位每一帧,相邻帧保留重叠区,杜绝接缝。
  • 智能滚动容器探测:Gmail、Slack 这类把滚动放在内部 <div> 里的页面也能正常长截图 —— 不是所有页面都滚 document。
  • 悬浮元素只留一次:长截图时 fixed / sticky 的导航栏只保留在首屏,不然会在每一屏顶部重复出现(可关掉)。
  • 截图期间隐藏滚动条,画面干净(可关掉)。
  • 进度气泡:页面上实时显示 正在截取整页 3/8,随时可以取消。
标注编辑器
编辑器的工具条:矩形 / 椭圆 / 箭头 / 画笔 / 马赛克 / 文字,8 色板 + 粗细调节,撤销重做,缩放,复制和下载

标注编辑器

截图完成后自动打开(可以关掉这个行为)。工具是矩形、椭圆、箭头、画笔、马赛克、文字,8 色色板加粗细调节。

快捷键手感做得比较接近设计软件:R/O/A/P/M/T 切工具,Ctrl+Z 撤销,Ctrl+Shift+Z 重做,Ctrl+S 下载,Ctrl+C 复制,0 恢复适应窗口。

「复制」写进剪贴板的是 PNG,可以直接粘进微信、飞书、文档里。这一步比看起来重要 —— 很多截图工具复制出来是位图的某种奇怪格式,粘到聊天窗口就糊了。

屏幕录制:给客户做演示用

这块是后来加的,但现在成了我用得最多的功能。给客户演示产品,不想装第三方录屏软件,就用它:

  1. 点扩展图标 → 红色「录制屏幕」按钮,或右键菜单 → 「录制屏幕(Demo 视频)」。
  2. Chrome 弹出原生选择器,挑整个屏幕 / 应用窗口 / 某个标签页。
  3. 录完有多种停止方式:点系统弹出的「● 正在录制屏幕」通知、页面右键菜单(录制中会自动变成「停止录制屏幕」)、或者打开扩展面板点停止。
  4. 停止后自动打开预览页,可以在线播放、下载 MP4/WebM,或者一键「再录一段」。
录制中的面板
录制中面板有计时器、暂停和停止。暂停时计时器同步冻结

输出 MP4 而不是 WebM

设置里有两种格式:

格式适合
MP4 · H.264(默认)微信 / 邮件 / 任何设备直接播放,给客户最省心。需要 Chrome 126+
WebM · VP9同码率体积更小,Chrome / Edge 环境播放。老版本浏览器的首选

浏览器不支持所选格式时会自动回退为另一种,不用手动干预。

录制完成预览页

有两个细节是专门为「发给客户」这个场景做的:

  • 有声音时 MP4 优先用 AAC。mime 里显式声明 mp4a.40.2,容器里才是真正的 AAC 音轨,微信、QuickTime、Windows 播放器都能出声。不声明的话 Chrome 会默认塞 Opus,苹果生态解不出来 —— 这就成了一个"我这边好好的,客户那边没声音"的问题。
  • MP4 优先用 avc3 编码。avc1 要求编解码描述(SPS/PPS)全程不变,录制中一旦捕获源分辨率变化(拖窗口、切全屏)就会中断录制;avc3 的参数集随码流更新,能容忍分辨率变化。不支持 avc3 的浏览器自动回退 avc1。

声音:麦克风旁白 + 标签页声音

录制时可以同时录麦克风旁白(给客户讲 Demo 时能听到你的讲解)和标签页/系统声音。两路会自动混音成一条音轨,不会互相覆盖。

录制声音设置
麦克风旁白开关与音量(20%–200%)、标签页/系统声音开关、麦克风授权与实时电平检测

有一个系统限制值得提前知道:macOS 的「整个屏幕 / 窗口」源本身不提供系统声音,这是 macOS 的限制不是扩展的问题。要录网页里的声音,得选「Chrome 标签页」并勾选「同时分享标签页音频」。

麦克风授权这里也有个 Chrome 的坑:audioCapture 权限只允许打包应用(packaged app)在 manifest 里声明,扩展写进去会被 Chrome 拒绝。所以授权只能发生在可见的扩展页面上 —— 首次录制时弹窗请求、或者设置页点「允许麦克风」、或者预览页点「授权麦克风并重录」,触发浏览器原生授权框,点一次「允许」之后长期有效。

真正执行录制的 offscreen 文档没有界面,弹不出授权框,所以这一步必须在可见页面上先完成。

没录到声音时不会静默产出一段哑巴视频 —— 预览页会直接标注「声音:麦克风旁白 / 无(默音)」,如果是没授权,还会给出就地授权并重录的入口。这个设计是从一次实际尴尬里改出来的。

预览页的声音标注

码率三档

档位码率适合
流畅2 Mbps纯操作演示、界面文字为主,文件最小
均衡(默认)6 Mbps常规产品 Demo,画质与体积兼顾
高清12 Mbps含动画 / 渐变 / 视频画面的精细演示

1080p 均衡档大约每分钟 45MB,长演示注意磁盘空间。

实现上最难的一块:MV3 不能录屏

Manifest V3 的 Service Worker 没有 DOM,而 MediaRecorder 需要 DOM 环境。这是整个项目最硬的约束,直接决定了架构。

解法是 offscreen document。 MV3 提供了一个专门的 chrome.offscreen API 来创建隐藏文档,它是有 DOM 的。录制引擎整个跑在 offscreen 文档里,直接调 getDisplayMedia() 弹出 Chrome 选择器并编码。

顺带说一句,这里没有用 chrome.desktopCapture —— 它返回的 streamId 被限制在目标标签页内,offscreen 文档消费不了。

其他几处不得不绕的地方:

captureVisibleTab 有每秒 2 次的配额限制(MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND)。长截图逐屏捕获必然撞上,所以引擎里内置了 ≥620ms 的节流和配额重试。

offscreen 文档每次录完就销毁。 因为它的媒体权限状态在创建时就定下来了,无界面也弹不了授权框。录制结束即销毁,用户授权之后下一次录制会自动用上新权限。

页面内的 UI 全部放在 closed Shadow DOM 里(气泡、工具条、框选遮罩),配 adoptedStyleSheets。这样不受页面样式和 CSP 影响,也不会污染页面。截图那一瞬间自动隐藏。

大图通过 IndexedDB 在 Service Worker / 弹窗 / 编辑器之间传递。 SW 没有 DOM 也没有 URL 可以挂 blob,拼接用 OffscreenCanvas,下载走 data URL,几百 KB 以上的图走 IndexedDB。

最小权限。 只申请 activeTab(用户点击或按快捷键时临时授权当前标签页),不申请 <all_urls> 常驻权限。

出问题的时候能自己看日志

截图或录制失败时最烦的是不知道哪一环断了。所以做了个诊断日志页,把全链路每一步都记下来:选源 → 注入页面 → 逐帧捕获 → 拼接/编码 → 保存。

诊断日志页
环境信息(扩展版本 / 浏览器 / 系统 / 屏幕)+ 最近 300 条运行日志,可按级别筛选、一键复制全部、清空

日志只存在本机 chrome.storage.local,不上传。入口在失败时弹窗底部出现的「查看日志」链接,或者设置页 →「诊断」。

多个上下文(SW / content script / offscreen / 编辑器)的日志会汇聚到 Service Worker 再统一落盘 —— 不然前端出的问题和后台出的问题分散在不同地方,排查起来更费劲。

安装

  1. 打开 chrome://extensions/
  2. 打开右上角开发者模式
  3. 点加载已解压的扩展程序,选仓库根目录
  4. 把图标固定到工具栏

改快捷键要去 chrome://extensions/shortcuts —— Chrome 不允许扩展直接改键位。设置页里会列出当前生效的键位并给直达按钮。录制命令没有默认快捷键,因为 Chrome 限制 commands 最多 4 个默认键,已经有 4 个了。

已知限制

  • chrome://、网上应用店这类浏览器内置页面无法截图,会明确提示而不是静默失败。
  • file:// 页面需要在扩展详情页开启「允许访问文件网址」。
  • 页面里随时间变化的区域(视频、动画、广告轮播)在长截图拼接结果里可能出现差异 —— 这是拼接的固有属性,不是 bug。
  • 声音:macOS 的「整个屏幕 / 窗口」源无系统声音,要录网页声音请选「Chrome 标签页」。
  • 录制时窗口最小化会导致捕获失败。

小结

这个扩展技术含量最高的是绕限制的部分:MV3 不给的东西(DOM、录屏、麦克风声明权限)都得靠 offscreen document、Web Audio 混音、以及「在可见页面上先拿授权」这几招拼出来。功能本身反而都好写。

做的过程中我记了一条规则,现在写别的扩展也在用:不要让用户面对一次「什么都没发生」。录到没声音就明说是默音并给重录入口,截图失败就给出具体是哪一环 —— 静默失败是最耗信任的一种 bug。

代码在 github.com/iamnicwu/nForce-screenCapture 。