在桌面应用开发中,截图功能是一个常见且重要的需求。无论是用于用户反馈、错误报告、内容分享,还是系统监控和演示,截图功能都能大大提升应用的实用性和用户体验。

image-20251124111357685
方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
webContents.capturePage() | 简单可靠,无需权限,捕获窗口内容 | 只能捕获当前窗口 | 窗口截图,推荐使用 |
desktopCapturer API | 可捕获屏幕和窗口,功能强大 | 需要用户授权(某些平台) | 屏幕截图,推荐使用 |
系统命令(screencapture/scrot) | 可获取系统级截图 | 需要解析输出,平台差异大 | 特殊需求 |
第三方截图库 | 功能完整 | 增加依赖,可能过度设计 | 复杂场景 |
采用Electron 原生 API 组合方案:
webContents.capturePage() APIdesktopCapturer API<img> 标签显示在主进程中实现截图功能的核心逻辑:
const { app, BrowserWindow, ipcMain, powerMonitor, Notification, dialog, desktopCapturer } = require('electron');
const path = require('path');
const fs = require('fs');
const os = require('os');
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
// 全局变量:主窗口
let mainWindow = null;
// 捕获当前窗口截图
ipcMain.handle('capture-window', async (event) => {
console.log('收到捕获窗口截图请求');
try {
if (!mainWindow || mainWindow.isDestroyed()) {
return { success: false, error: '主窗口不可用' };
}
// 使用webContents.capturePage()捕获当前窗口
const image = await mainWindow.webContents.capturePage();
// 转换为PNG格式的Buffer
const buffer = image.toPNG();
// 转换为base64
const base64 = buffer.toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
console.log('窗口截图已捕获,尺寸:', image.getSize());
return {
success: true,
dataUrl: dataUrl,
width: image.getSize().width,
height: image.getSize().height
};
} catch (error) {
console.error('捕获窗口截图失败:', error);
return { success: false, error: error.message };
}
});
// 获取可用的屏幕源(用于全屏截图)
ipcMain.handle('get-screen-sources', async (event) => {
console.log('收到获取屏幕源请求');
try {
const sources = await desktopCapturer.getSources({
types: ['screen', 'window'],
thumbnailSize: { width: 0, height: 0 }
});
console.log('可用的屏幕源数量:', sources.length);
return {
success: true,
sources: sources.map(source => ({
id: source.id,
name: source.name,
thumbnail: source.thumbnail.toDataURL()
}))
};
} catch (error) {
console.error('获取屏幕源失败:', error);
return { success: false, error: error.message };
}
});
// 捕获屏幕截图
ipcMain.handle('capture-screen', async (event, sourceId) => {
console.log('收到捕获屏幕截图请求,源ID:', sourceId);
try {
// 获取屏幕源,设置较大的thumbnailSize以获取高质量截图
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width: 1920, height: 1080 } // 设置较大的尺寸以获取高质量截图
});
let targetSource = null;
if (sourceId) {
targetSource = sources.find(s => s.id === sourceId);
}
// 如果没有指定源ID或找不到,使用第一个屏幕源
if (!targetSource && sources.length > 0) {
targetSource = sources[0];
}
if (!targetSource) {
return { success: false, error: '未找到可用的屏幕源' };
}
// 获取屏幕缩略图(NativeImage对象)
const thumbnail = targetSource.thumbnail;
// 转换为PNG格式的Buffer,然后转为base64
const buffer = thumbnail.toPNG();
const base64 = buffer.toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
const size = thumbnail.getSize();
console.log('屏幕截图已捕获,源:', targetSource.name, '尺寸:', size.width, 'x', size.height);
return {
success: true,
dataUrl: dataUrl,
width: size.width,
height: size.height,
sourceName: targetSource.name
};
} catch (error) {
console.error('捕获屏幕截图失败:', error);
return { success: false, error: error.message };
}
});
关键点说明:
webContents.capturePage() 捕获当前窗口NativeImage 对象,可转换为 PNG BufferdesktopCapturer.getSources() 获取屏幕源thumbnailSize 为较大值(1920x1080)以获取高质量截图在预加载脚本中暴露安全的 API 给渲染进程:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// ... 其他API ...
// 捕获当前窗口截图
captureWindow: () => {
return ipcRenderer.invoke('capture-window');
},
// 获取屏幕源
getScreenSources: () => {
return ipcRenderer.invoke('get-screen-sources');
},
// 捕获屏幕截图
captureScreen: (sourceId) => {
return ipcRenderer.invoke('capture-screen', sourceId);
},
// 保存图片(从base64数据)
saveImage: (base64Data, defaultFileName) => {
return ipcRenderer.invoke('save-image', base64Data, defaultFileName);
}
});
关键点说明:
contextBridge 安全地暴露 APIipcRenderer.invoke() 进行异步通信在渲染进程中实现用户界面和交互逻辑:
<!-- 截图按钮 -->
<button onclick="captureScreenshot()">📸 截图</button>
<!-- 截图容器 -->
<div id="screenshot-container" style="display: none; margin-top: 20px; padding: 20px; background: rgba(255, 255, 255, 0.1); border-radius: 10px; color: white; backdrop-filter: blur(10px); max-width: 800px; width: 90%;">
<h2 style="margin-bottom: 15px;">截图</h2>
<div id="screenshot-controls" style="margin-bottom: 15px;">
<button onclick="captureWindow()" style="margin-right: 10px;">🪟 截取窗口</button>
<button onclick="captureScreen()" style="margin-right: 10px;">🖥️ 截取屏幕</button>
<button onclick="closeScreenshot()">❌ 关闭</button>
</div>
<div id="screenshot-preview" style="margin-top: 15px; display: none;">
<h3 style="margin-bottom: 10px;">截图预览:</h3>
<img id="screenshot-image" style="max-width: 100%; border-radius: 8px; border: 2px solid rgba(255, 255, 255, 0.3);">
<div style="margin-top: 10px; color: rgba(255, 255, 255, 0.8);">
<div id="screenshot-info"></div>
</div>
<div style="margin-top: 10px;">
<button onclick="saveScreenshot()" style="margin-right: 10px;">💾 保存截图</button>
<button onclick="retakeScreenshot()">📸 重新截图</button>
</div>
</div>
<div id="screenshot-error" style="display: none; color: #ff6b6b; margin-top: 15px;"></div>
</div>
// 截图相关变量
let currentScreenshotData = null;
let currentScreenshotInfo = null;
// 打开截图功能
function captureScreenshot() {
const screenshotContainer = document.getElementById('screenshot-container');
screenshotContainer.style.display = 'block';
}
// 截取当前窗口
asyncfunction captureWindow() {
console.log('截取当前窗口');
const screenshotPreview = document.getElementById('screenshot-preview');
const screenshotImage = document.getElementById('screenshot-image');
const screenshotInfo = document.getElementById('screenshot-info');
const screenshotError = document.getElementById('screenshot-error');
screenshotError.style.display = 'none';
try {
if (window.electronAPI && window.electronAPI.captureWindow) {
const result = awaitwindow.electronAPI.captureWindow();
if (result.success) {
currentScreenshotData = result.dataUrl;
currentScreenshotInfo = {
type: '窗口',
width: result.width,
height: result.height
};
screenshotImage.src = result.dataUrl;
screenshotInfo.textContent = `类型: 窗口截图 | 尺寸: ${result.width} x ${result.height} 像素`;
screenshotPreview.style.display = 'block';
console.log('窗口截图已捕获');
} else {
thrownewError(result.error || '截取窗口失败');
}
} else {
thrownewError('截图功能不可用');
}
} catch (error) {
console.error('截取窗口失败:', error);
screenshotError.style.display = 'block';
screenshotError.textContent = `截取窗口失败: ${error.message}`;
screenshotPreview.style.display = 'none';
}
}
// 截取屏幕
asyncfunction captureScreen() {
console.log('截取屏幕');
const screenshotPreview = document.getElementById('screenshot-preview');
const screenshotImage = document.getElementById('screenshot-image');
const screenshotInfo = document.getElementById('screenshot-info');
const screenshotError = document.getElementById('screenshot-error');
screenshotError.style.display = 'none';
try {
if (window.electronAPI && window.electronAPI.captureScreen) {
// 不指定sourceId,使用默认屏幕
const result = awaitwindow.electronAPI.captureScreen(null);
if (result.success) {
currentScreenshotData = result.dataUrl;
currentScreenshotInfo = {
type: '屏幕',
width: result.width,
height: result.height,
sourceName: result.sourceName
};
screenshotImage.src = result.dataUrl;
screenshotInfo.textContent = `类型: 屏幕截图 | 尺寸: ${result.width} x ${result.height} 像素 | 源: ${result.sourceName || '默认屏幕'}`;
screenshotPreview.style.display = 'block';
console.log('屏幕截图已捕获');
} else {
thrownewError(result.error || '截取屏幕失败');
}
} else {
thrownewError('截图功能不可用');
}
} catch (error) {
console.error('截取屏幕失败:', error);
screenshotError.style.display = 'block';
screenshotError.textContent = `截取屏幕失败: ${error.message}`;
screenshotPreview.style.display = 'none';
}
}
// 保存截图
asyncfunction saveScreenshot() {
if (!currentScreenshotData) {
alert('没有可保存的截图');
return;
}
try {
// 生成文件名(带时间戳)
const timestamp = newDate().toISOString().replace(/[:.]/g, '-');
const fileName = `screenshot-${timestamp}.png`;
if (window.electronAPI && window.electronAPI.saveImage) {
const result = awaitwindow.electronAPI.saveImage(currentScreenshotData, fileName);
if (!result.canceled && result.filePath) {
if (result.saved) {
alert(`截图已保存到:\n${result.filePath}`);
} else {
alert(`请将截图保存到:\n${result.filePath}`);
}
}
} else {
// 降级方案:使用浏览器下载
const link = document.createElement('a');
link.download = fileName;
link.href = currentScreenshotData;
link.click();
alert('截图已下载');
}
} catch (error) {
console.error('保存截图失败:', error);
alert('保存截图失败: ' + error.message);
}
}
// 重新截图
function retakeScreenshot() {
const screenshotPreview = document.getElementById('screenshot-preview');
screenshotPreview.style.display = 'none';
currentScreenshotData = null;
currentScreenshotInfo = null;
console.log('准备重新截图');
}
// 关闭截图
function closeScreenshot() {
const screenshotContainer = document.getElementById('screenshot-container');
const screenshotPreview = document.getElementById('screenshot-preview');
const screenshotError = document.getElementById('screenshot-error');
screenshotContainer.style.display = 'none';
screenshotPreview.style.display = 'none';
screenshotError.style.display = 'none';
currentScreenshotData = null;
currentScreenshotInfo = null;
console.log('截图功能已关闭');
}
关键点说明:
currentScreenshotData 存储当前截图的 base64 数据currentScreenshotInfo 存储截图元信息在鸿蒙 PC 平台上,屏幕截图可能需要特殊权限。确保在 module.json5 中配置了必要的权限:
{
"requestPermissions": [
{
"name": "ohos.permission.CAPTURE_SCREEN",
"reason": "需要屏幕截图权限",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
注意:某些版本的鸿蒙 PC 可能不需要显式声明此权限,但建议添加以确保兼容性。
在鸿蒙 PC 平台上,desktopCapturer API 的行为可能与标准 Electron 略有不同:
thumbnailSize 参数的实际效果可能受系统限制建议:
// 在获取屏幕源时,使用较大的thumbnailSize以确保质量
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width: 1920, height: 1080 } // 根据实际需求调整
});
webContents.capturePage() 在鸿蒙 PC 平台上通常表现良好,但需要注意:
建议:
// 在截图前检查窗口状态
if (!mainWindow || mainWindow.isDestroyed()) {
return { success: false, error: '主窗口不可用' };
}
// 可以添加延迟以确保内容完全渲染
await new Promise(resolve => setTimeout(resolve, 100));
const image = await mainWindow.webContents.capturePage();
在鸿蒙 PC 平台上,大分辨率截图可能占用大量内存:
建议:
// 在保存截图后清理内存
async function saveScreenshot() {
// ... 保存逻辑 ...
// 保存后清理
if (currentScreenshotData) {
currentScreenshotData = null;
currentScreenshotInfo = null;
}
}
在鸿蒙 PC 平台上,文件保存路径可能需要特殊处理:
建议:
// 使用系统提供的用户目录
const { app } = require('electron');
const userDataPath = app.getPath('pictures'); // 或 'documents', 'downloads' 等
const defaultPath = path.join(userDataPath, `screenshot-${Date.now()}.png`);
在鸿蒙 PC 平台上,某些错误可能与标准平台不同:
建议:
// 添加详细的错误处理和日志
try {
const result = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width: 1920, height: 1080 }
});
if (result.length === 0) {
console.warn('未找到可用的屏幕源,可能是权限问题');
return { success: false, error: '未找到可用的屏幕源,请检查权限设置' };
}
} catch (error) {
console.error('获取屏幕源失败:', error);
// 根据错误类型提供不同的提示
if (error.message.includes('permission')) {
return { success: false, error: '需要屏幕截图权限,请在系统设置中授权' };
}
return { success: false, error: error.message };
}
在鸿蒙 PC 平台上测试截图功能时,建议:
问题 | 可能原因 | 解决方案 |
|---|---|---|
屏幕截图返回空白 | 权限未授权 | 引导用户在系统设置中授权 |
截图质量较低 | thumbnailSize 设置过小 | 增大 thumbnailSize 值 |
多显示器识别错误 | 屏幕源 ID 不稳定 | 使用屏幕名称而非 ID 进行匹配 |
内存占用过高 | 大分辨率截图 | 限制最大截图尺寸或使用压缩 |
// 1. 截取当前窗口
const windowResult = awaitwindow.electronAPI.captureWindow();
if (windowResult.success) {
console.log('窗口截图尺寸:', windowResult.width, 'x', windowResult.height);
// 显示截图
document.getElementById('preview').src = windowResult.dataUrl;
}
// 2. 截取屏幕
const screenResult = awaitwindow.electronAPI.captureScreen(null);
if (screenResult.success) {
console.log('屏幕截图尺寸:', screenResult.width, 'x', screenResult.height);
// 显示截图
document.getElementById('preview').src = screenResult.dataUrl;
}
// 3. 保存截图
const saveResult = awaitwindow.electronAPI.saveImage(
windowResult.dataUrl,
'screenshot.png'
);
if (saveResult.saved) {
console.log('截图已保存到:', saveResult.filePath);
}
// 获取所有可用的屏幕源
const sourcesResult = awaitwindow.electronAPI.getScreenSources();
if (sourcesResult.success) {
sourcesResult.sources.forEach(source => {
console.log('屏幕源:', source.name, 'ID:', source.id);
});
// 截取指定的屏幕
if (sourcesResult.sources.length > 0) {
const screenResult = awaitwindow.electronAPI.captureScreen(
sourcesResult.sources[0].id
);
}
}
本文详细介绍了如何在 Electron 应用中实现截图功能,包括窗口截图和屏幕截图两种模式。实现方案使用 Electron 原生 API,无需额外依赖,具有良好的跨平台兼容性。
webContents.capturePage(),简单可靠,无需权限desktopCapturer API,功能强大,支持多显示器desktopCapturer 在鸿蒙 PC 上的行为差异通过本文的实现方案,您可以在 Electron 应用中快速集成截图功能,并在鸿蒙 PC 平台上获得良好的用户体验。
参考资料
[1]
Electron Desktop Capturer API: https://www.electronjs.org/docs/latest/api/desktop-capturer
[2]
Electron WebContents capturePage: https://www.electronjs.org/docs/latest/api/web-contents#contentscapturepagerect
[3]
Electron NativeImage: https://www.electronjs.org/docs/latest/api/native-image
[4]
鸿蒙应用开发文档: https://developer.harmonyos.com/