Electron屏幕捕获:实现屏幕截图与录屏功能
在桌面应用开发中,屏幕捕获是一个常见且重要的功能需求。无论是用于远程协助、教学演示、游戏录制还是应用监控,屏幕截图和录屏功能都扮演着关键角色。Electron作为跨平台桌面应用开发框架,提供了强大的desktopCapturer API来实现这些功能。
本文将深入探讨如何在Electron应用中实现专业的屏幕捕获功能,包括截图、录屏以及相关的权限处理和优化技巧。
屏幕捕获技术概览
Electron的屏幕捕获功能基于Chromium的桌面捕获API,主要通过desktopCapturer模块实现。该模块允许应用访问桌面上的媒体源,包括屏幕和应用程序窗口。
核心API介绍
const { desktopCapturer } = require('electron')
// 获取可用的媒体源
desktopCapturer.getSources({
types: ['screen', 'window'],
thumbnailSize: { width: 150, height: 150 },
fetchWindowIcons: true
}).then(sources => {
console.log('Available sources:', sources)
})
实现屏幕截图功能
基础截图实现
以下是一个完整的屏幕截图示例,包含主进程、预加载脚本和渲染进程的完整实现:
// main.js - 主进程
const { app, BrowserWindow, desktopCapturer, ipcMain, shell } = require('electron')
const fs = require('fs').promises
const path = require('path')
const os = require('os')
let mainWindow = null
// 计算截图尺寸
function calculateScreenshotSize(devicePixelRatio) {
const { width, height } = require('electron').screen.getPrimaryDisplay().workAreaSize
const maxDimension = Math.max(width, height)
return {
width: maxDimension * devicePixelRatio,
height: maxDimension * devicePixelRatio
}
}
// 截图处理函数
async function takeScreenshot(devicePixelRatio) {
try {
const thumbSize = calculateScreenshotSize(devicePixelRatio)
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: thumbSize
})
// 查找主屏幕
const primaryScreen = sources.find(source =>
source.name.toLowerCase().includes('screen') ||
source.name.toLowerCase().includes('entire')
)
if (primaryScreen) {
const screenshotPath = path.join(os.tmpdir(), `screenshot_${Date.now()}.png`)
await fs.writeFile(screenshotPath, primaryScreen.thumbnail.toPNG())
// 在文件管理器中打开截图
shell.showItemInFolder(screenshotPath)
return { success: true, path: screenshotPath }
}
return { success: false, error: 'No screen found' }
} catch (error) {
return { success: false, error: error.message }
}
}
// IPC处理器
ipcMain.handle('take-screenshot', async (event, devicePixelRatio) => {
return await takeScreenshot(devicePixelRatio)
})
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
enableRemoteModule: false
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(createWindow)
// preload.js - 预加载脚本
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
takeScreenshot: () => ipcRenderer.invoke('take-screenshot', window.devicePixelRatio)
})
<!-- index.html - 渲染进程界面 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>屏幕截图工具</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; }
button {
background: #007acc; color: white; border: none;
padding: 12px 24px; border-radius: 6px; cursor: pointer;
font-size: 16px; margin: 10px 0;
}
button:hover { background: #005a9e; }
.status { margin: 10px 0; color: #666; }
.error { color: #d13438; }
.success { color: #107c10; }
</style>
</head>
<body>
<div class="container">
<h1>Electron屏幕截图工具</h1>
<button id="screenshotBtn">拍摄屏幕截图</button>
<div id="status" class="status"></div>
</div>
<script src="renderer.js"></script>
</body>
</html>
// renderer.js - 渲染进程逻辑
const screenshotBtn = document.getElementById('screenshotBtn')
const statusDiv = document.getElementById('status')
screenshotBtn.addEventListener('click', async () => {
statusDiv.textContent = '正在捕获屏幕...'
statusDiv.className = 'status'
try {
const result = await window.electronAPI.takeScreenshot()
if (result.success) {
statusDiv.textContent = `截图已保存: ${result.path}`
statusDiv.className = 'status success'
} else {
statusDiv.textContent = `错误: ${result.error}`
statusDiv.className = 'status error'
}
} catch (error) {
statusDiv.textContent = `捕获失败: ${error.message}`
statusDiv.className = 'status error'
}
})
高级截图功能
多屏幕支持
// 支持多显示器截图
async function captureAllScreens(devicePixelRatio) {
const displays = require('electron').screen.getAllDisplays()
const screenshots = []
for (const display of displays) {
const thumbSize = {
width: display.bounds.width * devicePixelRatio,
height: display.bounds.height * devicePixelRatio
}
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: thumbSize
})
const screenSource = sources.find(source =>
source.display_id === display.id.toString()
)
if (screenSource) {
const screenshotPath = path.join(os.tmpdir(), `screen_${display.id}_${Date.now()}.png`)
await fs.writeFile(screenshotPath, screenSource.thumbnail.toPNG())
screenshots.push({ display: display.id, path: screenshotPath })
}
}
return screenshots
}
区域截图
// 实现区域截图功能
async function captureRegion(devicePixelRatio, region) {
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: {
width: require('electron').screen.getPrimaryDisplay().bounds.width * devicePixelRatio,
height: require('electron').screen.getPrimaryDisplay().bounds.height * devicePixelRatio
}
})
const primaryScreen = sources.find(source =>
source.name.toLowerCase().includes('screen')
)
if (primaryScreen) {
const image = primaryScreen.thumbnail
const { x, y, width, height } = region
// 创建canvas进行区域裁剪
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
ctx.drawImage(image, x, y, width, height, 0, 0, width, height)
return canvas.toDataURL('image/png')
}
return null
}
实现屏幕录制功能
基础录屏实现
// 屏幕录制功能
class ScreenRecorder {
constructor() {
this.mediaRecorder = null
this.recordedChunks = []
this.isRecording = false
}
async startRecording() {
try {
// 获取屏幕流
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: 'always',
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30 }
},
audio: true
})
this.mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm; codecs=vp9',
videoBitsPerSecond: 5000000
})
this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.recordedChunks.push(event.data)
}
}
this.mediaRecorder.onstop = () => {
this.saveRecording()
}
this.mediaRecorder.start(1000) // 每1秒收集一次数据
this.isRecording = true
} catch (error) {
console.error('Recording failed:', error)
throw error
}
}
stopRecording() {
if (this.mediaRecorder && this.isRecording) {
this.mediaRecorder.stop()
this.isRecording = false
// 停止所有轨道
this.mediaRecorder.stream.getTracks().forEach(track => track.stop())
}
}
async saveRecording() {
const blob = new Blob(this.recordedChunks, { type: 'video/webm' })
const buffer = await blob.arrayBuffer()
// 通过IPC发送到主进程保存
const result = await window.electronAPI.saveVideo(buffer, 'recording.webm')
if (result.success) {
console.log('Recording saved:', result.path)
} else {
console.error('Save failed:', result.error)
}
this.recordedChunks = []
}
}
录屏功能集成
// 主进程录屏支持
const { ipcMain } = require('electron')
const fs = require('fs').promises
ipcMain.handle('save-video', async (event, buffer, filename) => {
try {
const videosDir = path.join(os.homedir(), 'Videos')
await fs.mkdir(videosDir, { recursive: true })
const filePath = path.join(videosDir, filename)
await fs.writeFile(filePath, Buffer.from(buffer))
return { success: true, path: filePath }
} catch (error) {
return { success: false, error: error.message }
}
})
// 预加载脚本暴露API
contextBridge.exposeInMainWorld('recorderAPI', {
saveVideo: (buffer, filename) =>
ipcRenderer.invoke('save-video', buffer, filename)
})
权限处理与用户体验
macOS权限处理
// 检查屏幕录制权限
async function checkScreenRecordingPermission() {
if (process.platform === 'darwin') {
const { systemPreferences } = require('electron')
const status = await systemPreferences.getMediaAccessStatus('screen')
if (status !== 'granted') {
// 请求权限
const granted = await systemPreferences.askForMediaAccess('screen')
if (!granted) {
throw new Error('Screen recording permission denied')
}
}
}
}
// 在应用启动时检查权限
app.whenReady().then(async () => {
await checkScreenRecordingPermission()
createWindow()
})
Windows权限处理
// Windows系统权限检查
function checkWindowsPermissions() {
if (process.platform === 'win32') {
// Windows通常不需要特殊权限,但可以检查其他条件
const version = os.release()
console.log('Windows version:', version)
}
}
性能优化与最佳实践
内存管理
// 优化内存使用的截图功能
class OptimizedScreenshot {
constructor() {
this.screenshotCache = new Map()
this.cacheTimeout = 30000 // 30秒缓存
}
async getScreenshot(forceRefresh = false) {
const cacheKey = 'current_screen'
if (!forceRefresh && this.screenshotCache.has(cacheKey)) {
const cached = this.screenshotCache.get(cacheKey)
if (Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data
}
}
const screenshot = await this.captureScreen()
this.screenshotCache.set(cacheKey, {
data: screenshot,
timestamp: Date.now()
})
return screenshot
}
async captureScreen() {
// 实现截图逻辑
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width: 800, height: 600 } // 优化尺寸
})
return sources[0]?.thumbnail.toPNG()
}
clearCache() {
this.screenshotCache.clear()
}
}
错误处理与重试机制
// 健壮的错误处理
async function robustScreenCapture(maxRetries = 3) {
let lastError = null
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width: 150, height: 150 }
})
} catch (error) {
lastError = error
console.warn(`Attempt ${attempt} failed:`, error.message)
if (attempt < maxRetries) {
// 指数退避重试
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
)
}
}
}
throw lastError
}
安全考虑
内容安全策略
<!-- 安全的内容安全策略 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
">
安全的IPC通信
// 安全的IPC通信验证
ipcMain.handle('secure-screenshot', (event, ...args) => {
// 验证请求来源
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



