{T}

原生能力概览

Electron 赋予 Web 开发者使用前端技术构建跨平台桌面应用的能力。其核心优势在于,它不仅提供了强大的 Web 技术栈,还允许深入操作系统底层,调用丰富的原生功能。

系统架构

plaintext
┌─────────────────────────────────────────────────────────┐
│                   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 应用的基石,它负责创建和管理应用的窗口。每个窗口都是一个独立的浏览器实例,拥有自己的渲染进程。

javascript
// 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")

关键参数详解

参数类型默认值说明
widthNumber800窗口宽度(像素)
heightNumber600窗口高度(像素)
xNumber居中窗口 X 坐标
yNumber居中窗口 Y 坐标
frameBooleantrue是否显示边框
transparentBooleanfalse是否透明(需 frame: false)
resizableBooleantrue是否可调整大小
movableBooleantrue是否可移动
minimizableBooleantrue是否可最小化
maximizableBooleantrue是否可最大化
closableBooleantrue是否可关闭
alwaysOnTopBooleanfalse是否始终置顶
fullscreenBooleanfalse是否全屏
kioskBooleanfalse是否启用 Kiosk 模式
webPreferencesObject-Web 功能配置

webPreferences 配置

参数类型默认值说明安全建议
nodeIntegrationBooleanfalse是否启用 Node.js⚠️ 建议设为 false
contextIsolationBooleantrue是否开启上下文隔离✅ 强烈建议保持 true
preloadString-预加载脚本路径✅ 推荐使用
webSecurityBooleantrue是否启用 Web 安全✅ 建议保持 true
allowRunningInsecureContentBooleanfalse允许加载不安全内容❌ 不建议开启
enableRemoteModuleBooleanfalse是否启用 remote 模块❌ 已废弃,不建议使用
sandboxBooleanfalse是否启用沙箱✅ 建议开启

使用场景

  • 创建应用主窗口
  • 创建子窗口、弹窗
  • 创建无边框窗口(自定义标题栏)
  • 创建透明窗口(悬浮窗)
  • 创建 Kiosk 模式窗口(信息展示)

Tray - 系统托盘

Tray 模块用于在操作系统的通知区域创建一个图标。

使用场景:适用于需要常驻后台的应用,如音乐播放器、聊天工具、下载工具等,方便用户快速访问。

javascript
// 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)
})

平台差异

功能macOSWindowsLinux
托盘位置右上角菜单栏右下角任务栏系统托盘区域
图标尺寸16x16 或 22x2216x16视桌面环境而定
Template Image✅ 支持❌ 不支持❌ 不支持
setTitle✅ 支持❌ 不支持❌ 不支持

Notification - 系统通知

Notification 模块允许向用户发送操作系统的原生通知。

使用场景:用于消息提醒、任务完成提示、系统状态更新等。

javascript
// 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("新消息", "您有一条新的未读消息。")

平台差异

功能macOSWindowsLinux
基本通知
自定义图标⚠️ 部分支持
操作按钮
输入框
声音⚠️ 视系统而定

Menu 模块用于创建原生应用菜单和上下文菜单。

使用场景:构建应用的顶部菜单栏(如"文件"、"编辑"),或在特定元素上右键显示上下文菜单。

javascript
// 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 来显示原生的系统对话框。

使用场景:当需要用户进行文件操作(打开、保存)或需要向用户显示重要的同步信息时。

javascript
// 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 模块提供了对系统剪贴板的读写操作。

使用场景:实现复制、粘贴文本、图片等功能。

javascript
// 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()

支持格式

格式macOSWindowsLinux
text/plain
text/html
image/png
image/jpeg
text/uri-list
public.bookmark

GlobalShortcut - 全局快捷键

globalShortcut 模块允许注册/注销在应用失去焦点时也能响应的全局键盘快捷键。

使用场景:为应用设置全局快捷操作,如"老板键"、快速启动等。

javascript
// 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 模块用于获取关于屏幕尺寸、显示器、鼠标位置等信息。

使用场景:窗口管理、多屏应用布局、截图工具等。

javascript
// 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 模块可以获取桌面上正在运行的窗口、屏幕或单个标签页的音视频流。

使用场景:实现屏幕录制、远程协助、视频会议等功能。

注意:此模块只能在渲染进程中使用。

javascript
// 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
    }
  }
})

跨平台兼容性

平台判断

javascript
const isMac = process.platform === 'darwin'
const isWindows = process.platform === 'win32'
const isLinux = process.platform === 'linux'
 
// 根据平台执行不同逻辑
if (isMac) {
  // macOS 特定代码
} else if (isWindows) {
  // Windows 特定代码
} else {
  // Linux 特定代码
}

平台差异对照表

功能特性macOSWindowsLinux
系统托盘位置右上角菜单栏右下角任务栏系统托盘区域
菜单栏位置屏幕顶部窗口顶部窗口顶部
Dock 菜单✅ 支持❌ 不支持❌ 不支持
任务栏进度条❌ 不支持✅ 支持⚠️ 部分支持
通知操作按钮✅ 支持✅ 支持❌ 不支持
全局快捷键✅ 支持✅ 支持✅ 支持
透明窗口✅ 支持⚠️ 部分支持⚠️ 视环境而定

跨平台最佳实践

  1. 使用 CmdOrCtrl 快捷键修饰符:自动适配 macOS 的 Command 和 Windows/Linux 的 Control
  2. 提供平台特定的 UI:根据平台调整菜单、快捷键、托盘图标
  3. 测试所有平台:确保功能在所有目标平台上正常工作
  4. 处理平台差异:使用条件判断处理平台特定功能

性能优化建议

1. 窗口管理优化

javascript
// 延迟加载窗口内容
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. 事件监听优化

javascript
// 避免重复注册事件监听器
// 错误示例
function handleClick() {
  button.on('click', () => {})  // 每次调用都会注册新的监听器
}
 
// 正确示例
function setupListeners() {
  button.on('click', handleClick)
}

3. IPC 通信优化

javascript
// 批量发送消息,减少通信次数
ipcRenderer.send('batch-update', { data1, data2, data3 })
 
// 使用 invoke 替代 send/on 模式
const result = await ipcRenderer.invoke('get-data')

安全注意事项

1. 禁用不安全选项

javascript
// 安全的 BrowserWindow 配置
const win = new BrowserWindow({
  webPreferences: {
    nodeIntegration: false,           // ❌ 不要启用
    contextIsolation: true,            // ✅ 必须启用
    enableRemoteModule: false,         // ❌ 不要启用
    sandbox: true,                     // ✅ 建议启用
    webSecurity: true,                 // ✅ 必须启用
    allowRunningInsecureContent: false // ❌ 不要启用
  }
})

2. 验证 IPC 消息

javascript
// 主进程:验证消息来源
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

javascript
// 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. 应用生命周期管理

javascript
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. 单实例应用

javascript
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. 错误处理

javascript
// 全局错误处理
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. 如何隐藏窗口而不是关闭?

javascript
let mainWindow
 
mainWindow.on('close', (event) => {
  if (!app.isQuitting) {
    event.preventDefault()
    mainWindow.hide()
  }
})
 
app.on('before-quit', () => {
  app.isQuitting = true
})

2. 如何自定义窗口标题栏?

javascript
const win = new BrowserWindow({
  frame: false,  // 无边框窗口
  titleBarStyle: 'hidden',  // macOS: 隐藏标题栏但保留控制按钮
  webPreferences: {
    preload: path.join(__dirname, 'preload.js')
  }
})

3. 如何实现应用开机自启动?

javascript
const { app } = require('electron')
const path = require('path')
 
// 设置开机自启动
app.setLoginItemSettings({
  openAtLogin: true,
  path: app.getPath('exe'),
  args: ['--start-minimized']
})
 
// 取消开机自启动
app.setLoginItemSettings({
  openAtLogin: false
})

4. 如何获取应用版本信息?

javascript
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.chrome

5. 如何处理文件关联?

javascript
// 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. 如何实现深色模式适配?

javascript
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'

相关文档

参考链接