主题切换
进阶专题:屏幕控制与自动化
本专题将探索 Electron 最强大的能力之一——对屏幕和输入设备的控制。从截图、录屏到模拟鼠标键盘操作,你将学会如何让应用"看见"屏幕并"操作"系统,这是构建 RPA(机器人流程自动化)和自动化工具的核心基础。
进阶-1 截图功能(desktopCapturer)
Electron 内置的 desktopCapturer 模块可以捕获屏幕和窗口的画面,这是实现截图、录屏、屏幕分享的基础。
💡 给前端开发者的建议
把 desktopCapturer 想象成浏览器的 getDisplayMedia API,但它能捕获整个屏幕(包括其他应用窗口),不受浏览器安全限制。
获取屏幕源
javascript
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('screenAPI', {
getSources: () => ipcRenderer.invoke('screen:getSources'),
captureScreen: (sourceId) => ipcRenderer.invoke('screen:capture', sourceId),
saveScreenshot: (base64Data) => ipcRenderer.invoke('screen:saveScreenshot', base64Data),
})进阶-2 模拟鼠标操作(@nut-tree/nut.js)
要实现自动化操作,我们需要控制鼠标和键盘。@nut-tree/nut.js 是一个强大的跨平台自动化库,支持 Windows、macOS 和 Linux。
安装依赖
bashnpm install @nut-tree/nut-js # 或 npm install robotjs # 备选方案,但 nut.js 更现代版本锁定建议
@nut-tree/nut-js依赖原生模块,版本变化可能导致 API 不兼容。建议在package.json中锁定主版本号(如"@nut-tree/nut-js": "~3.0.0")。
鼠标基本操作
javascript
const {
mouse,
left,
right,
straightTo,
Point,
Button
} = require('@nut-tree/nut-js');
// 设置鼠标移动速度
mouse.config.mouseSpeed = 500; // 像素/秒
// 移动鼠标到指定坐标
async function moveMouse(x, y) {
await mouse.move(straightTo(new Point(x, y)));
}
// 点击操作(含错误处理)
async function mouseClick(x, y, button = left) {
try {
await moveMouse(x, y);
await mouse.click(button);
} catch (err) {
console.error(`鼠标点击 (${x}, ${y}) 失败:`, err.message);
throw err;
}
}
// 双击
async function mouseDoubleClick(x, y) {
await moveMouse(x, y);
await mouse.doubleClick(left);
}
// 拖拽
async function mouseDrag(fromX, fromY, toX, toY) {
await mouse.move(straightTo(new Point(fromX, fromY)));
await mouse.pressButton(left);
await mouse.move(straightTo(new Point(toX, toY)));
await mouse.releaseButton(left);
}
// 滚动
async function mouseScroll(amount) {
await mouse.scrollDown(amount); // 向下滚动
// await mouse.scrollUp(amount); // 向上滚动
}
// 获取当前鼠标位置
async function getMousePosition() {
return await mouse.getPosition();
}重要提示
鼠标自动化操作需要在主进程或工作线程中执行,不能在渲染进程中直接调用。因为渲染进程运行在 Chromium 沙箱中,无法直接控制操作系统输入设备。
平台兼容性说明
| 功能 | Windows | macOS | Linux |
|---|---|---|---|
| 鼠标控制 | 无需额外权限 | 需要辅助功能权限 | 需要 libXtst 依赖 |
| 键盘模拟 | 无需额外权限 | 同鼠标控制权限 | 需要 libXtst 依赖 |
| 屏幕捕获 | 无需额外权限 | macOS 10.15+ 需要屏幕录制权限 | 需要 libX11 依赖 |
| 窗口控制 | 支持 | 部分功能受限 | 支持 |
macOS 权限提示
首次使用自动化功能时,macOS 会弹出权限请求对话框。用户必须在 系统设置 → 隐私与安全性 → 辅助功能 中手动授权,才能使用 nut.js 的鼠标和键盘控制功能。如果用户拒绝,自动化 API 将抛出异常。建议在应用启动时检查权限状态并引导用户开启。
Linux 依赖安装
bash
# Ubuntu/Debian
sudo apt install libxtst-dev libpng++-dev
# Fedora
sudo dnf install libXtst-devel libpng-devel进阶-3 模拟键盘输入
javascript
const { keyboard, Key } = require('@nut-tree/nut-js');
// 输入文本
async function typeText(text) {
await keyboard.type(text);
}
// 按下单个按键
async function pressKey(key) {
await keyboard.pressKey(key);
await keyboard.releaseKey(key);
}
// 组合键
async function hotkey(keys) {
await keyboard.pressKey(...keys);
await keyboard.releaseKey(...keys);
}
// 常用快捷键示例
async function copy() {
await hotkey([Key.LeftControl, Key.C]); // Windows/Linux
// await hotkey([Key.LeftCommand, Key.C]); // macOS
}
async function paste() {
await hotkey([Key.LeftControl, Key.V]);
}
async function selectAll() {
await hotkey([Key.LeftControl, Key.A]);
}
async function pressEnter() {
await pressKey(Key.Enter);
}
async function pressTab() {
await pressKey(Key.Tab);
}
// 实际应用:自动填写表单
async function fillForm(data) {
await typeText(data.name);
await pressTab();
await typeText(data.email);
await pressTab();
await typeText(data.phone);
await pressTab();
await pressEnter();
}进阶-4 获取屏幕信息
了解屏幕的分辨率、缩放比例等信息,对于精确控制鼠标位置和适配不同显示器至关重要。
javascript
const { screen } = require('electron');
// 获取所有显示器信息
function getDisplayInfo() {
const displays = screen.getAllDisplays();
return displays.map((display, index) => ({
id: display.id,
index: index,
// 物理分辨率
size: {
width: display.size.width,
height: display.size.height
},
// 工作区(排除任务栏/菜单栏)
workArea: {
x: display.workArea.x,
y: display.workArea.y,
width: display.workArea.width,
height: display.workArea.height
},
// 缩放比例(DPI 缩放)
scaleFactor: display.scaleFactor,
// 是否为主显示器
isPrimary: display.isPrimary,
// 旋转角度
rotation: display.rotation,
// 触控支持
touchSupport: display.touchSupport,
// 实际可用分辨率(考虑缩放)
logicalSize: {
width: display.size.width / display.scaleFactor,
height: display.size.height / display.scaleFactor
}
}));
}
// 获取当前鼠标所在的显示器
function getDisplayAtCursor() {
const point = screen.getCursorScreenPoint();
return screen.getDisplayNearestPoint(point);
}
// 获取主显示器
function getPrimaryDisplay() {
return screen.getPrimaryDisplay();
}
// 监听显示器变化
screen.on('display-added', (event, display) => {
console.log('新显示器连接:', display.id);
});
screen.on('display-removed', (event, display) => {
console.log('显示器断开:', display.id);
});
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
console.log('显示器参数变化:', changedMetrics);
});缩放比例处理
Windows 和 macOS 都支持 DPI 缩放(如 125%、150%)。scaleFactor 告诉你当前缩放比例,nut.js 的坐标是物理像素,所以高 DPI 屏幕需要特别注意坐标转换。
进阶-5 窗口控制
获取系统中所有窗口列表,并控制指定窗口(激活、移动、关闭等),是实现自动化工作流的重要能力。
| 功能 | 实现方式 | 平台支持 |
|---|---|---|
| 获取窗口列表 | desktopCapturer + 原生模块 | 全平台 |
| 激活窗口 | nut.js window.focus() | 全平台 |
| 移动窗口 | nut.js window.move() | 全平台 |
| 调整窗口大小 | nut.js window.resize() | 全平台 |
| 获取窗口位置 | nut.js window.getPosition() | 全平台 |
javascript
const {
getWindows,
getActiveWindow,
focus,
move,
resize
} = require('@nut-tree/nut-js').window;
// 获取所有窗口
async function listWindows() {
const windows = await getWindows();
return Promise.all(windows.map(async (win) => {
const title = await win.title;
const region = await win.region;
return {
title,
x: region.left,
y: region.top,
width: region.width,
height: region.height
};
}));
}
// 根据标题查找并激活窗口
async function activateWindow(titlePattern) {
const windows = await getWindows();
for (const win of windows) {
const title = await win.title;
if (title.includes(titlePattern)) {
await focus(win);
return true;
}
}
return false;
}
// 移动窗口到指定位置
async function moveWindow(title, x, y) {
const windows = await getWindows();
for (const win of windows) {
const winTitle = await win.title;
if (winTitle.includes(title)) {
await move(win, { x, y });
return true;
}
}
return false;
}进阶-6 OCR 文字识别(Tesseract.js)
OCR(光学字符识别)让应用能够"读懂"屏幕上的文字,这是自动化工具的关键能力。
安装 Tesseract.js
bashnpm install tesseract.js # 下载中文语言包(训练数据) # 自动下载,也可手动放置到项目目录
javascript
const Tesseract = require('tesseract.js');
const path = require('path');
const fs = require('fs').promises;
// 注意:nut.js 的 screen 对象用于获取鼠标位置和屏幕尺寸,
// 而 Electron 的 screen 模块用于获取显示器详细信息(如 getAllDisplays)
const { screen } = require('@nut-tree/nut-js');
const { screen: electronScreen } = require('electron');
// 从图片文件识别文字
async function recognizeFromFile(imagePath) {
const result = await Tesseract.recognize(
imagePath,
'chi_sim+eng', // 中文简体 + 英文
{
logger: m => console.log(m), // 进度回调
errorHandler: err => console.error(err)
}
);
return {
text: result.data.text,
confidence: result.data.confidence,
words: result.data.words
};
}
// 从屏幕区域截图并识别
async function recognizeFromScreen(x, y, width, height) {
// 截取屏幕区域
const region = new screen.Region(x, y, width, height);
const image = await screen.capture(region);
// 保存临时图片
const tempPath = path.join(app.getPath('temp'), 'ocr-temp.png');
await image.toFile(tempPath);
// 识别
const result = await recognizeFromFile(tempPath);
// 清理临时文件
await fs.unlink(tempPath);
return result;
}
// 在屏幕上查找指定文字的位置
async function findTextOnScreen(targetText) {
// 截取全屏
const displays = electronScreen.getAllDisplays();
const primary = displays.find(d => d.isPrimary);
const result = await recognizeFromScreen(
0, 0,
primary.size.width,
primary.size.height
);
// 查找目标文字
const foundWords = result.words.filter(word =>
word.text.includes(targetText)
);
return foundWords.map(word => ({
text: word.text,
x: word.bbox.x0,
y: word.bbox.y0,
width: word.bbox.x1 - word.bbox.x0,
height: word.bbox.y1 - word.bbox.y0,
confidence: word.confidence
}));
}性能优化
OCR 识别速度取决于图片大小和文字数量。建议先裁剪出感兴趣区域(ROI)再识别,全屏识别可能需要几秒时间。
进阶-7 录屏功能基础
基于 desktopCapturer 和 MediaRecorder API,我们可以实现基础的屏幕录制功能。
vue
<template>
<div class="screen-recorder">
<h3>屏幕录制</h3>
<select v-model="selectedSource" class="source-select">
<option value="">选择屏幕源</option>
<option v-for="source in sources" :key="source.id" :value="source.id">
{{ source.name }}
</option>
</select>
<div class="controls">
<button @click="startRecording" :disabled="isRecording || !selectedSource">开始录制</button>
<button @click="stopRecording" :disabled="!isRecording">停止录制</button>
</div>
<video v-if="recordedUrl" :src="recordedUrl" controls class="preview" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const sources = ref([])
const selectedSource = ref('')
const isRecording = ref(false)
const recordedUrl = ref('')
let mediaRecorder = null
let recordedChunks = []
onMounted(async () => {
sources.value = await window.electronAPI.screen?.getSources?.() || []
})
async function startRecording() {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: selectedSource.value
}
}
})
recordedChunks = []
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9'
})
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data)
}
}
mediaRecorder.onstop = async () => {
const blob = new Blob(recordedChunks, { type: 'video/webm' })
recordedUrl.value = URL.createObjectURL(blob)
// 保存到文件(通过 IPC 发送二进制数据到主进程)
const buffer = Buffer.from(await blob.arrayBuffer())
await window.electronAPI.saveRecording?.('recording.webm', buffer)
// 停止所有轨道
stream.getTracks().forEach(track => track.stop())
}
mediaRecorder.start(1000) // 每秒收集一次数据
isRecording.value = true
}
function stopRecording() {
if (mediaRecorder && isRecording.value) {
mediaRecorder.stop()
isRecording.value = false
}
}
</script>
<style scoped>
.screen-recorder {
padding: 16px;
}
.source-select {
width: 100%;
padding: 8px;
margin-bottom: 16px;
}
.controls {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.controls button {
padding: 8px 24px;
}
.preview {
width: 100%;
max-height: 400px;
}
</style>进阶-8 实际案例:自动截图 + OCR 识别
让我们整合本章所学,实现一个"自动识别屏幕上指定区域文字"的实用工具:
javascript
// ===== 注意:AutoOCR 功能需要拆分为主进程和渲染进程两部分 =====
// 以下代码仅为演示逻辑,实际开发时需通过 IPC 桥接两个进程
// ─── 主进程部分 (main.js / ocr-main.js) ───
const { app, desktopCapturer, nativeImage } = require('electron');
const Tesseract = require('tesseract.js');
const fs = require('fs').promises;
const path = require('path');
class AutoOCRMain {
constructor() {
this.language = 'chi_sim+eng';
}
// 识别图片中的文字(在主进程中执行 Tesseract,避免阻塞 UI)
async recognize(imagePath) {
const result = await Tesseract.recognize(imagePath, this.language);
return {
text: result.data.text.trim(),
confidence: result.data.confidence,
words: result.data.words.map(w => ({
text: w.text,
confidence: w.confidence,
bbox: w.bbox
}))
};
}
// 保存截图并识别(主进程负责文件 I/O)
async saveAndRecognize(imageDataUrl) {
const timestamp = Date.now();
// 注意:Buffer 只在主进程/预加载脚本中可用,渲染进程需通过 IPC 传递
const buffer = Buffer.from(imageDataUrl.split(',')[1], 'base64');
const screenshotPath = path.join(app.getPath('temp'), `ocr-${timestamp}.png`);
await fs.writeFile(screenshotPath, buffer);
const result = await this.recognize(screenshotPath);
return { ...result, screenshotPath };
}
}
// ─── 渲染进程部分 (renderer.js / Vue 组件) ───
// 以下代码在渲染进程中运行,使用浏览器 API 截图
class AutoOCRRenderer {
constructor() {
this.language = 'chi_sim+eng';
}
// 截图指定区域(使用 Web API,仅在渲染进程中可用)
async captureRegion(sourceId, x, y, width, height) {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId
}
}
});
const video = document.createElement('video');
video.srcObject = stream;
await video.play();
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, x, y, width, height, 0, 0, width, height);
stream.getTracks().forEach(track => track.stop());
return canvas.toDataURL('image/png');
}
// 截图并通过 IPC 交给主进程识别
async screenshotAndRecognize(sourceId, region) {
console.log('正在截图...');
const imageData = await this.captureRegion(
sourceId, region.x, region.y, region.width, region.height
);
// 通过 IPC 将图片数据发送给主进程进行 OCR 识别和文件保存
const result = await window.electronAPI.ocrRecognize(imageData);
return result;
}
}
// ─── IPC 桥接 (preload.js) ───
// contextBridge.exposeInMainWorld('electronAPI', {
// ocrRecognize: (imageData) => ipcRenderer.invoke('ocr:recognize', imageData)
// });
// ─── 使用示例(渲染进程)───
async function demo() {
const ocr = new AutoOCRRenderer();
// 获取屏幕源(通过 IPC 调用 desktopCapturer)
const sources = await window.electronAPI.getScreenSources();
const primaryScreen = sources[0];
// 识别屏幕左上角 400x200 区域的文字
const result = await ocr.screenshotAndRecognize(
primaryScreen.id,
{ x: 0, y: 0, width: 400, height: 200 }
);
console.log('识别结果:', result.text);
console.log('置信度:', result.confidence);
console.log('截图保存:', result.screenshotPath);
}
module.exports = { AutoOCRMain, AutoOCRRenderer };应用场景
这个自动截图+OCR的组合可以应用于:自动读取发票信息、识别验证码、提取屏幕上的错误信息、自动化数据录入等场景。
专题小结
本专题掌握了 Electron 屏幕控制与自动化的核心能力:
- desktopCapturer:捕获屏幕和窗口画面
- nut.js:模拟鼠标移动、点击、拖拽
- 键盘模拟:输入文本、快捷键组合
- 屏幕信息:分辨率、缩放、多显示器管理
- 窗口控制:获取窗口列表、激活、移动窗口
- Tesseract.js:OCR 文字识别
- MediaRecorder:屏幕录制基础
这些能力为第 10 章的 RPA 桌面助手项目奠定了坚实的技术基础。掌握屏幕控制和自动化,你的应用就拥有了"眼睛"和"双手"。