截图与桌面捕获:捕获屏幕与窗口
本教程共 45 篇 · 第 28 篇 · 更新于 2026-08-03
28. 截图与桌面捕获:采集屏幕与窗口
本节目标
- 用
desktopCapturer.getSources获取屏幕与窗口列表及缩略图。 - 理解
getDisplayMedia与setDisplayMediaRequestHandler的实时采集流程。 - 把捕获到的流接入
<video>做本地预览。 - 了解 macOS、Windows 的授权与音频捕获限制。
- 掌握沙箱下如何安全地在渲染进程触发捕获。
1-1 desktopCapturer 的定位
desktopCapturer 用来列出屏幕上可捕获的「媒体源」:整个屏幕,或者某个应用窗口。它在主进程运行,是一个偏底层的模块。
最常见的两个用途:一是生成屏幕/窗口的缩略图,做「选择要共享哪个画面」的界面;二是配合 WebRTC 的 getDisplayMedia,把桌面画面作为视频流采集进应用,用于录屏、直播推流等。
需要强调:单纯调用 desktopCapturer 并不会「自动开始录像」。它只是提供源信息和缩略图,真正的画面流要靠 WebRTC 的 getUserMedia / getDisplayMedia 拿到。
1-2 获取屏幕与窗口列表
getSources(options) 接受一个配置对象,返回一个 Promise,解析为 DesktopCapturerSource 数组。关键选项有:
types:要枚举的类型,数组里可含'screen'与'window'。thumbnailSize:缩略图缩放尺寸,默认 150×150;不需要缩略图时把宽或高设为 0 可省性能。fetchWindowIcons:是否取窗口图标,默认false。
每个 source 带有 id、name 以及 thumbnail(一个 NativeImage)。下面演示列出所有屏幕与窗口并打印名称。
// 主进程 main.js
const { desktopCapturer } = require('electron/main')
async function listSources() {
const sources = await desktopCapturer.getSources({
types: ['screen', 'window'],
thumbnailSize: { width: 150, height: 150 }
})
for (const source of sources) {
console.log(source.id, source.name)
// source.thumbnail 是 NativeImage,可转 DataURL 显示
const url = source.thumbnail.toDataURL()
}
}
如果渲染进程需要这些数据,应通过 preload 暴露一个安全方法,由主进程调用 getSources 后回传结果,而不是把模块直接暴露给网页。
// 预加载脚本 preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('capturer', {
listSources: () => ipcRenderer.invoke('list-sources')
})
// 主进程 main.js
const { ipcMain, desktopCapturer } = require('electron/main')
ipcMain.handle('list-sources', () => {
return desktopCapturer.getSources({
types: ['screen', 'window'],
thumbnailSize: { width: 150, height: 150 }
}).then((sources) =>
sources.map((s) => ({
id: s.id,
name: s.name,
thumb: s.thumbnail.toDataURL()
}))
)
})
1-3 在 WebRTC 中采集实时画面
要拿到可播放、可录制的实时视频流,推荐走 Web 标准的 navigator.mediaDevices.getDisplayMedia。为了让它在 Electron 里自动选用某个桌面源,需要在主进程注册 setDisplayMediaRequestHandler。
该处理器在渲染进程请求显示媒体时被调用,你在里面用 desktopCapturer.getSources 选源,并通过 callback 把选中的源交还给浏览器媒体管线。
// 主进程 main.js
const { app, BrowserWindow, desktopCapturer, session } = require('electron/main')
app.whenReady().then(() => {
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: './preload.js'
}
})
session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
// 这里直接授予第一个屏幕;真实场景应让用户选择
callback({ video: sources[0], audio: 'loopback' })
})
}, { useSystemPicker: true })
mainWindow.loadFile('index.html')
})
// 渲染进程 renderer.js
const video = document.querySelector('video')
document.getElementById('start').addEventListener('click', async () => {
const stream = await navigator.mediaDevices.getDisplayMedia({
audio: true,
video: { width: 320, height: 240, frameRate: 30 }
})
video.srcObject = stream
video.onloadedmetadata = () => video.play()
})
<!-- index.html -->
<button id="start">开始捕获</button>
<video width="320" height="240" autoplay></video>
<script src="renderer.js"></script>
注意 getDisplayMedia 不允许用 deviceId 指定源(这是 Web 规范限制),源的选择权实际在主进程的 setDisplayMediaRequestHandler 里。
1-4 缩略图与选择的界面
一个实用的做法:先用 getSources 拿到各源的缩略图,渲染成可点击的网格,用户点选后再开始 getDisplayMedia。这样既友好又符合权限预期。
// 渲染进程 renderer.js
const sources = await window.capturer.listSources()
const grid = document.getElementById('grid')
for (const s of sources) {
const btn = document.createElement('button')
btn.innerHTML = `<img src="${s.thumb}"><span>${s.name}</span>`
btn.addEventListener('click', () => startCapture(s.id))
grid.appendChild(btn)
}
这里再次体现 IPC 的价值:缩略图由主进程生成,页面只拿到无害的 DataURL 与 id,无法直接操作底层模块。
1-5 权限与平台限制
桌面捕获涉及隐私,各平台都有门槛,必须在打包与 Info.plist 层面做好准备。
macOS 10.15 Catalina 起,捕获屏幕需要用户在「系统设置—隐私与安全性—屏幕录制」中授权。可先用上一章的 systemPreferences.getMediaAccessStatus('screen') 预检状态。
macOS 14.2 Sonoma 起,捕获系统音频必须配置 NSAudioCaptureUsageDescription 这个 Info.plist 键;若从终端或 IDE 启动 Electron,则要求父程序也带该键,否则音频流会静默失败且不报错。
Windows 上 getSources 可用,但 screen 与 window 类型的选取逻辑与系统版本相关。Linux 使用 PipeWire 时,getSources 通常只返回单一源,且屏幕与窗口会合并为窗口捕获。
1-6 录制与导出思路
拿到 MediaStream 后,可用 MediaRecorder 把它录成 WebM 等格式,再经 IPC 交给主进程写盘。这是纯前端能力,沙箱下也能工作。
// 渲染进程 renderer.js
let recorder
function startCapture(sourceId) {
navigator.mediaDevices.getDisplayMedia({ video: true }).then((stream) => {
recorder = new MediaRecorder(stream)
recorder.ondataavailable = (e) => {
// e.data 是 Blob,可经 IPC 发主进程保存
}
recorder.start()
})
}
如果要录的是「应用内画面」而非桌面,也可考虑 contents.debugger 或 webContents 的快照能力,但那是更进阶的话题,本教程点到为止。
1-7 采集参数与音频选项
getDisplayMedia 的约束对象决定了捕获质量。你可以指定 width、height、frameRate、cursor 等,约束越精细,对性能与体积的影响越明显。一般的录屏 30fps、1080p 已足够清晰。
音频方面,macOS 14.2 起需要在 Info.plist 提供 NSAudioCaptureUsageDescription 才能捕获系统声音。若只想要麦克风而不抓系统声,可在约束里给 audio 设 true,但同样受系统授权约束。
// 渲染进程 renderer.js
navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 30, width: 1920, height: 1080 },
audio: false
}).then((stream) => {
// 只录屏幕画面,不含声音
})
捕获到的流是标准 MediaStream,除了接 <video> 预览,也能喂给 MediaRecorder 生成录像文件,或推送到 WebRTC 对端做实时共享。无论哪种用途,源的选择与授权都先在系统层完成,应用层只需消费这条流。
关于「选哪个源」的体验,推荐在让用户选择之前先用 getSources 生成缩略图网格,并把选中结果缓存下来。当用户再次点击「开始共享」时,直接用上次选择的 id 在 setDisplayMediaRequestHandler 里匹配,省去重复弹窗。注意 useSystemPicker: true 是实验特性,系统选择器可用时会优先用它,否则仍走你的 callback,两种路径都要兼容。
若你的应用仅需在窗口内展示某个窗口的实时画面(而非录制),也可考虑用 BrowserWindow 的 webContents 捕获或 desktopCapturer 拿到的缩略图轮询刷新,但实时性与性能都不如 getDisplayMedia 的流方案,按场景权衡即可。
1-8 局限与注意事项
桌面捕获有几个常被忽略的边界。受数字版权保护(DRM)的窗口或视频内容通常无法被捕获,缩略图可能呈现为黑屏,这是系统层面而非代码层面的限制,需要在界面上提前说明。
thumbnailSize 越大,生成缩略图占用的内存越高。不需要预览时把宽或高设为 0 即可跳过缩略图生成,窗口很多时能省下可观开销。
拿到 MediaStream 后,停止共享要主动释放资源:调用 stream.getTracks().forEach((t) => t.stop()),否则摄像头或屏幕可能会被持续占用,下一次捕获也会受影响。
多显示器环境下 getSources 会返回每个屏幕的源,选择界面的 name 要清晰地告诉用户哪一个是哪一块屏,避免选错。
常见误区
不要把 desktopCapturer 当成录像机。它只负责列源和缩略图,真正的流来自 WebRTC,二者要配合使用。
不要忽视 macOS 的屏幕录制授权。未授权时 getSources 可能返回空或失败,应在 UI 里引导用户去系统设置开启。
不要以为 useSystemPicker: true 在所有平台都可用。它是实验特性,系统选择器不可用时仍会走你自己的 callback 逻辑,代码要为两种路径都做好准备。