原生能力概览
Electron 赋予 Web 开发者使用前端技术构建跨平台桌面应用的能力。其核心优势在于,它不仅提供了强大的 Web 技术栈,还允许深入操作系统底层,调用丰富的原生功能。
系统架构
┌─────────────────────────────────────────────────────────┐
│ Electron 原生能力 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ GUI 组件 │ │ 系统集成 │ │ 底层 API │ │
│ ├──────────────┤ ├──────────────┤ ├──────────────┤ │
│ │ BrowserWindow│ │ Clipboard │ │ GlobalShortcut│ │
│ │ Tray │ │ Screen │ │ PowerMonitor │ │
│ │ Menu │ │ DesktopCaptur│ │ Process │ │
│ │ Dialog │ │ Notification │ │ Shell │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
├─────────────────────────────────────────────────────────┤
│ Node.js 运行时 │
│ • 文件系统 (fs) • 子进程 (child_process) │
│ • 网络 (net/http) • 加密 (crypto) │
└─────────────────────────────────────────────────────────┘API 分类概览
按功能分类
| 分类 | 模块 | 主要功能 | 使用场景 |
|---|---|---|---|
| 窗口管理 | BrowserWindow | 创建和管理应用窗口 | 应用主窗口、子窗口、无边框窗口 |
| BrowserView | 在窗口中嵌入网页 | 浏览器标签页、嵌入式内容 | |
| 系统集成 | Tray | 系统托盘图标 | 后台运行应用、快速访问 |
| Menu | 应用菜单和上下文菜单 | 应用菜单栏、右键菜单 | |
| Dialog | 原生对话框 | 文件选择、消息提示 | |
| Notification | 系统通知 | 消息提醒、任务通知 | |
| 剪贴板 | Clipboard | 剪贴板读写 | 复制粘贴、截图分享 |
| 屏幕相关 | Screen | 屏幕信息获取 | 多屏布局、窗口定位 |
| DesktopCapturer | 桌面捕获 | 屏幕截图、屏幕录制 | |
| 快捷键 | GlobalShortcut | 全局快捷键 | 系统级快捷操作 |
| 进程管理 | App | 应用生命周期 | 启动、退出、单实例 |
| Process | 进程信息 | 获取进程 ID、版本 | |
| 系统交互 | Shell | 系统集成 | 打开文件、URL |
| PowerMonitor | 电源状态 | 监听休眠、电池状态 |
按进程分类
| 进程类型 | 可用模块 | 说明 |
|---|---|---|
| 主进程 | 所有模块 | 完整的 Node.js 能力和 Electron API |
| 渲染进程 | clipboard, desktopCapturer, Notification (部分) | 有限的 API,需要通过 IPC 与主进程通信 |
| Preload 脚本 | 所有模块(受限) | 通过 contextBridge 安全暴露 API |
原生 GUI 能力
Electron 允许开发者通过 JavaScript API 调用操作系统的原生图形用户界面(GUI)组件,从而创建出与本地应用体验一致的界面。
BrowserWindow - 应用窗口
BrowserWindow 是 Electron 应用的基石,它负责创建和管理应用的窗口。每个窗口都是一个独立的浏览器实例,拥有自己的渲染进程。
// main.js
const { BrowserWindow } = require("electron")
const path = require("path")
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 开启 Node.js 集成
nodeIntegration: true,
// 关闭上下文隔离
contextIsolation: false,
// 预加载脚本
preload: path.join(__dirname, "preload.js")
}
})
win.loadFile("index.html")关键参数详解:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width | Number | 800 | 窗口宽度(像素) |
height | Number | 600 | 窗口高度(像素) |
x | Number | 居中 | 窗口 X 坐标 |
y | Number | 居中 | 窗口 Y 坐标 |
frame | Boolean | true | 是否显示边框 |
transparent | Boolean | false | 是否透明(需 frame: false) |
resizable | Boolean | true | 是否可调整大小 |
movable | Boolean | true | 是否可移动 |
minimizable | Boolean | true | 是否可最小化 |
maximizable | Boolean | true | 是否可最大化 |
closable | Boolean | true | 是否可关闭 |
alwaysOnTop | Boolean | false | 是否始终置顶 |
fullscreen | Boolean | false | 是否全屏 |
kiosk | Boolean | false | 是否启用 Kiosk 模式 |
webPreferences | Object | - | Web 功能配置 |
webPreferences 配置:
| 参数 | 类型 | 默认值 | 说明 | 安全建议 |
|---|---|---|---|---|
nodeIntegration | Boolean | false | 是否启用 Node.js | ⚠️ 建议设为 false |
contextIsolation | Boolean | true | 是否开启上下文隔离 | ✅ 强烈建议保持 true |
preload | String | - | 预加载脚本路径 | ✅ 推荐使用 |
webSecurity | Boolean | true | 是否启用 Web 安全 | ✅ 建议保持 true |
allowRunningInsecureContent | Boolean | false | 允许加载不安全内容 | ❌ 不建议开启 |
enableRemoteModule | Boolean | false | 是否启用 remote 模块 | ❌ 已废弃,不建议使用 |
sandbox | Boolean | false | 是否启用沙箱 | ✅ 建议开启 |
使用场景:
- 创建应用主窗口
- 创建子窗口、弹窗
- 创建无边框窗口(自定义标题栏)
- 创建透明窗口(悬浮窗)
- 创建 Kiosk 模式窗口(信息展示)
Tray - 系统托盘
Tray 模块用于在操作系统的通知区域创建一个图标。
使用场景:适用于需要常驻后台的应用,如音乐播放器、聊天工具、下载工具等,方便用户快速访问。
// main.js
const { Tray, Menu, app, nativeImage } = require("electron")
const path = require("path")
let tray = null
app.whenReady().then(() => {
// 创建托盘图标
const icon = nativeImage.createFromPath(
path.join(__dirname, "icon.png")
).resize({ width: 16, height: 16 })
tray = new Tray(icon)
// 创建托盘菜单
const contextMenu = Menu.buildFromTemplate([
{
label: "显示窗口",
type: "normal",
click: () => {
mainWindow.show()
}
},
{ type: "separator" },
{ label: "退出", type: "normal", role: "quit" }
])
tray.setToolTip("我的应用")
tray.setContextMenu(contextMenu)
})平台差异:
| 功能 | macOS | Windows | Linux |
|---|---|---|---|
| 托盘位置 | 右上角菜单栏 | 右下角任务栏 | 系统托盘区域 |
| 图标尺寸 | 16x16 或 22x22 | 16x16 | 视桌面环境而定 |
| Template Image | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
| setTitle | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
Notification - 系统通知
Notification 模块允许向用户发送操作系统的原生通知。
使用场景:用于消息提醒、任务完成提示、系统状态更新等。
// main.js 或 renderer.js
const { Notification } = require("electron")
function showNotification(title, body) {
const notify = new Notification({
title: title,
body: body,
icon: path.join(__dirname, "icon.png"),
silent: false // 是否静音
})
notify.show()
notify.on("click", () => {
console.log("通知被点击")
// 显示主窗口
mainWindow.show()
})
notify.on("close", () => {
console.log("通知被关闭")
})
}
showNotification("新消息", "您有一条新的未读消息。")平台差异:
| 功能 | macOS | Windows | Linux |
|---|---|---|---|
| 基本通知 | ✅ | ✅ | ✅ |
| 自定义图标 | ✅ | ✅ | ⚠️ 部分支持 |
| 操作按钮 | ✅ | ✅ | ❌ |
| 输入框 | ❌ | ✅ | ❌ |
| 声音 | ✅ | ✅ | ⚠️ 视系统而定 |
Menu - 自定义菜单
Menu 模块用于创建原生应用菜单和上下文菜单。
使用场景:构建应用的顶部菜单栏(如"文件"、"编辑"),或在特定元素上右键显示上下文菜单。
// main.js
const { Menu, app } = require("electron")
function createMenu() {
const template = [
{
label: "文件",
submenu: [
{
label: "新建",
accelerator: "CmdOrCtrl+N",
click: () => {
// 新建文件逻辑
}
},
{ type: "separator" },
app.isPackaged
? { role: "quit", label: "退出" }
: { role: "close", label: "关闭" }
]
},
{
label: "编辑",
submenu: [
{ role: "undo", label: "撤销" },
{ role: "redo", label: "重做" },
{ type: "separator" },
{ role: "cut", label: "剪切" },
{ role: "copy", label: "复制" },
{ role: "paste", label: "粘贴" },
{ role: "selectAll", label: "全选" }
]
},
{
label: "视图",
submenu: [
{ role: "reload", label: "重新加载" },
{ role: "forceReload", label: "强制重新加载" },
{ type: "separator" },
{ role: "resetZoom", label: "实际大小" },
{ role: "zoomIn", label: "放大" },
{ role: "zoomOut", label: "缩小" },
{ type: "separator" },
{ role: "togglefullscreen", label: "全屏" }
]
},
{
label: "帮助",
submenu: [
{
label: "关于",
click: () => {
// 显示关于对话框
}
}
]
}
]
// macOS 特殊处理:添加应用菜单
if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services', submenu: [] },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
})
}
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}Dialog - 原生对话框
dialog 模块提供了 API 来显示原生的系统对话框。
使用场景:当需要用户进行文件操作(打开、保存)或需要向用户显示重要的同步信息时。
// main.js
const { dialog } = require("electron")
// 显示打开文件对话框
async function showOpenDialog() {
const result = await dialog.showOpenDialog({
title: "选择文件",
defaultPath: app.getPath("documents"),
filters: [
{ name: "文本文件", extensions: ["txt", "md"] },
{ name: "图片", extensions: ["jpg", "png", "gif"] },
{ name: "所有文件", extensions: ["*"] }
],
properties: ["openFile", "multiSelections"]
})
if (!result.canceled) {
console.log("选择的文件:", result.filePaths)
return result.filePaths
}
return null
}
// 显示保存文件对话框
async function showSaveDialog() {
const result = await dialog.showSaveDialog({
title: "保存文件",
defaultPath: "my-file.txt",
filters: [
{ name: "Text Files", extensions: ["txt"] },
{ name: "All Files", extensions: ["*"] }
]
})
if (!result.canceled && result.filePath) {
console.log("保存路径:", result.filePath)
return result.filePath
}
return null
}
// 显示消息对话框
async function showMessageDialog() {
const result = await dialog.showMessageBox({
type: "question",
buttons: ["取消", "确定"],
defaultId: 1,
title: "确认",
message: "确定要删除这个文件吗?",
detail: "此操作不可撤销"
})
console.log("用户选择:", result.response) // 0: 取消, 1: 确定
}
// 显示错误对话框
dialog.showErrorBox("错误", "发生了一个错误")对话框类型:
| 方法 | 用途 | 返回值 |
|---|---|---|
showOpenDialog | 打开文件/目录对话框 | { canceled, filePaths } |
showSaveDialog | 保存文件对话框 | { canceled, filePath } |
showMessageBox | 消息对话框 | { response, checkboxChecked } |
showErrorBox | 错误对话框 | void |
showCertificateTrustDialog | 证书信任对话框 | void |
操作系统底层 API
除了 GUI 组件,Electron 还封装一系列 API,用于与操作系统的底层功能进行交互。
Clipboard - 系统剪贴板
clipboard 模块提供了对系统剪贴板的读写操作。
使用场景:实现复制、粘贴文本、图片等功能。
// main.js 或 renderer.js
const { clipboard, nativeImage } = require("electron")
// 读写文本
clipboard.writeText("这是要复制的文本")
console.log(clipboard.readText())
// 读写图片
const image = nativeImage.createFromPath("/path/to/image.png")
clipboard.writeImage(image)
const copiedImage = clipboard.readImage()
// 写入多种格式
clipboard.write({
text: "Hello World",
html: "<b>Hello World</b>",
image: nativeImage.createFromPath("/path/to/image.png")
})
// 清空剪贴板
clipboard.clear()支持格式:
| 格式 | macOS | Windows | Linux |
|---|---|---|---|
text/plain | ✅ | ✅ | ✅ |
text/html | ✅ | ✅ | ✅ |
image/png | ✅ | ✅ | ✅ |
image/jpeg | ✅ | ✅ | ✅ |
text/uri-list | ✅ | ✅ | ✅ |
public.bookmark | ✅ | ❌ | ❌ |
GlobalShortcut - 全局快捷键
globalShortcut 模块允许注册/注销在应用失去焦点时也能响应的全局键盘快捷键。
使用场景:为应用设置全局快捷操作,如"老板键"、快速启动等。
// main.js
const { app, globalShortcut } = require("electron")
app.whenReady().then(() => {
// 注册快捷键
const ret = globalShortcut.register("CommandOrControl+Shift+X", () => {
console.log("全局快捷键被触发")
// 执行相应操作
})
if (!ret) {
console.log("快捷键注册失败")
}
// 检查快捷键是否已注册
console.log(
"快捷键已注册:",
globalShortcut.isRegistered("CommandOrControl+Shift+X")
)
})
app.on("will-quit", () => {
// 注销所有快捷键
globalShortcut.unregisterAll()
})注意事项:
- 全局快捷键会覆盖系统快捷键,谨慎使用
- 应用退出时必须注销快捷键
- 不同平台的快捷键组合可能不同
Screen - 屏幕信息
screen 模块用于获取关于屏幕尺寸、显示器、鼠标位置等信息。
使用场景:窗口管理、多屏应用布局、截图工具等。
// main.js
const { screen } = require("electron")
// 获取主显示器信息
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
console.log(`屏幕可用尺寸: ${width}x${height}`)
// 获取所有显示器信息
const allDisplays = screen.getAllDisplays()
console.log(`检测到 ${allDisplays.length} 个显示器`)
allDisplays.forEach((display, index) => {
console.log(`显示器 ${index + 1}:`)
console.log(` 分辨率: ${display.bounds.width}x${display.bounds.height}`)
console.log(` 位置: (${display.bounds.x}, ${display.bounds.y})`)
console.log(` 缩放因子: ${display.scaleFactor}`)
})
// 获取鼠标当前位置
const point = screen.getCursorScreenPoint()
console.log(`鼠标位置: (${point.x}, ${point.y})`)
// 监听显示器变化
screen.on("display-added", (event, newDisplay) => {
console.log("新增显示器:", newDisplay.id)
})
screen.on("display-removed", (event, oldDisplay) => {
console.log("移除显示器:", oldDisplay.id)
})
screen.on("display-metrics-changed", (event, display, changedMetrics) => {
console.log("显示器属性变化:", changedMetrics)
})DesktopCapturer - 音视频捕捉
desktopCapturer 模块可以获取桌面上正在运行的窗口、屏幕或单个标签页的音视频流。
使用场景:实现屏幕录制、远程协助、视频会议等功能。
注意:此模块只能在渲染进程中使用。
// renderer.js
const { desktopCapturer } = require("electron")
desktopCapturer.getSources({ types: ["window", "screen"] }).then(async (sources) => {
for (const source of sources) {
console.log("源名称:", source.name)
console.log("源 ID:", source.id)
// 选择要捕捉的窗口或屏幕
if (source.name === "Entire screen" || source.name === "整个屏幕") {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: source.id,
minWidth: 1280,
maxWidth: 1920,
minHeight: 720,
maxHeight: 1080
}
}
})
// 将视频流附加到 <video> 元素
const videoElement = document.querySelector("video")
videoElement.srcObject = stream
videoElement.onloadedmetadata = () => videoElement.play()
} catch (e) {
console.error("getUserMedia error:", e)
}
return
}
}
})跨平台兼容性
平台判断
const isMac = process.platform === 'darwin'
const isWindows = process.platform === 'win32'
const isLinux = process.platform === 'linux'
// 根据平台执行不同逻辑
if (isMac) {
// macOS 特定代码
} else if (isWindows) {
// Windows 特定代码
} else {
// Linux 特定代码
}平台差异对照表
| 功能特性 | macOS | Windows | Linux |
|---|---|---|---|
| 系统托盘位置 | 右上角菜单栏 | 右下角任务栏 | 系统托盘区域 |
| 菜单栏位置 | 屏幕顶部 | 窗口顶部 | 窗口顶部 |
| Dock 菜单 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
| 任务栏进度条 | ❌ 不支持 | ✅ 支持 | ⚠️ 部分支持 |
| 通知操作按钮 | ✅ 支持 | ✅ 支持 | ❌ 不支持 |
| 全局快捷键 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 透明窗口 | ✅ 支持 | ⚠️ 部分支持 | ⚠️ 视环境而定 |
跨平台最佳实践
- 使用
CmdOrCtrl快捷键修饰符:自动适配 macOS 的 Command 和 Windows/Linux 的 Control - 提供平台特定的 UI:根据平台调整菜单、快捷键、托盘图标
- 测试所有平台:确保功能在所有目标平台上正常工作
- 处理平台差异:使用条件判断处理平台特定功能
性能优化建议
1. 窗口管理优化
// 延迟加载窗口内容
function createWindow() {
const win = new BrowserWindow({
show: false, // 先不显示
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// 页面加载完成后再显示
win.once('ready-to-show', () => {
win.show()
})
win.loadFile('index.html')
}
// 及时销毁不再使用的窗口
win.on('closed', () => {
win = null
})2. 事件监听优化
// 避免重复注册事件监听器
// 错误示例
function handleClick() {
button.on('click', () => {}) // 每次调用都会注册新的监听器
}
// 正确示例
function setupListeners() {
button.on('click', handleClick)
}3. IPC 通信优化
// 批量发送消息,减少通信次数
ipcRenderer.send('batch-update', { data1, data2, data3 })
// 使用 invoke 替代 send/on 模式
const result = await ipcRenderer.invoke('get-data')安全注意事项
1. 禁用不安全选项
// 安全的 BrowserWindow 配置
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: false, // ❌ 不要启用
contextIsolation: true, // ✅ 必须启用
enableRemoteModule: false, // ❌ 不要启用
sandbox: true, // ✅ 建议启用
webSecurity: true, // ✅ 必须启用
allowRunningInsecureContent: false // ❌ 不要启用
}
})2. 验证 IPC 消息
// 主进程:验证消息来源
ipcMain.handle('sensitive-operation', (event, data) => {
// 验证发送者
const webContents = event.sender
const mainWindow = BrowserWindow.getAllWindows()[0]
if (webContents !== mainWindow.webContents) {
throw new Error('未授权的请求')
}
// 验证数据格式
if (typeof data !== 'object' || data === null) {
throw new Error('无效的数据格式')
}
// 执行操作
return doSensitiveOperation(data)
})3. 使用 contextBridge
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
// 只暴露必要的 API
openFile: () => ipcRenderer.invoke('dialog:openFile'),
readFile: (path) => ipcRenderer.invoke('file:read', path),
writeFile: (path, content) => ipcRenderer.invoke('file:write', path, content)
})最佳实践
1. 应用生命周期管理
const { app, BrowserWindow } = require('electron')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
mainWindow.on('closed', () => {
mainWindow = null
})
}
// 应用准备就绪
app.whenReady().then(() => {
createWindow()
// macOS: 点击 Dock 图标时重新创建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
// 所有窗口关闭时退出应用(Windows & Linux)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})2. 单实例应用
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 当运行第二个实例时,聚焦到已有窗口
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
})
app.whenReady().then(() => {
createWindow()
})
}3. 错误处理
// 全局错误处理
process.on('uncaughtException', (error) => {
console.error('未捕获的异常:', error)
// 可以选择退出应用或继续运行
})
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise 拒绝:', reason)
})
// 窗口错误处理
mainWindow.webContents.on('crashed', (event, killed) => {
console.log('渲染进程崩溃:', killed ? '被杀死' : '意外崩溃')
// 重启窗口或显示错误信息
})
mainWindow.webContents.on('render-process-gone', (event, details) => {
console.log('渲染进程退出:', details)
})常见问题解答
1. 如何隐藏窗口而不是关闭?
let mainWindow
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault()
mainWindow.hide()
}
})
app.on('before-quit', () => {
app.isQuitting = true
})2. 如何自定义窗口标题栏?
const win = new BrowserWindow({
frame: false, // 无边框窗口
titleBarStyle: 'hidden', // macOS: 隐藏标题栏但保留控制按钮
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})3. 如何实现应用开机自启动?
const { app } = require('electron')
const path = require('path')
// 设置开机自启动
app.setLoginItemSettings({
openAtLogin: true,
path: app.getPath('exe'),
args: ['--start-minimized']
})
// 取消开机自启动
app.setLoginItemSettings({
openAtLogin: false
})4. 如何获取应用版本信息?
const { app } = require('electron')
// 获取应用版本
const version = app.getVersion()
// 获取 Electron 版本
const electronVersion = process.versions.electron
// 获取 Node.js 版本
const nodeVersion = process.versions.node
// 获取 Chrome 版本
const chromeVersion = process.versions.chrome5. 如何处理文件关联?
// macOS: 在 Info.plist 中配置
// Windows: 在注册表中配置
// 在应用中处理文件打开
app.on('open-file', (event, path) => {
event.preventDefault()
openFile(path)
})
app.on('open-url', (event, url) => {
event.preventDefault()
handleUrl(url)
})6. 如何实现深色模式适配?
const { nativeTheme } = require('electron')
// 检测当前主题
console.log('当前主题:', nativeTheme.shouldUseDarkColors ? '深色' : '浅色')
// 监听主题变化
nativeTheme.on('updated', () => {
console.log('主题已切换')
mainWindow.webContents.send('theme-changed', nativeTheme.shouldUseDarkColors)
})
// 设置主题源
nativeTheme.themeSource = 'system' // 'system', 'light', 'dark'