Electron 创建和管理窗口

在 Electron 中,窗口(Window)是应用与用户交互的核心载体。

所有可视界面都运行在 渲染进程(Renderer Process) 中,而这些窗口的创建与控制则由 主进程(Main Process) 负责。


创建和管理窗口

Electron 通过 BrowserWindow 类来创建窗口。

主进程中通过以下方式定义一个新窗口:

实例

const { BrowserWindow } = require('electron')

let mainWindow = new BrowserWindow({
  width: 1200,
  height: 800,
  show: false, // 等页面准备好再显示
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
  },
})

BrowserWindow 配置选项

  • width / height:窗口宽高。
  • minWidth / minHeight:最小尺寸限制。
  • resizable:是否允许调整大小。
  • fullscreen / fullscreenable:是否全屏。
  • frame:是否显示原生标题栏(常用于自定义标题栏时关闭)。
  • transparent:透明窗口(可实现毛玻璃、浮动等效果)。
  • alwaysOnTop:窗口是否总在最前。

窗口尺寸、位置与状态
可以通过 API 控制窗口状态:

mainWindow.maximize()   // 最大化
mainWindow.minimize()   // 最小化
mainWindow.restore()    // 还原
mainWindow.setBounds({ x: 100, y: 100, width: 800, height: 600 }) // 调整位置和大小

还可监听窗口变化:

mainWindow.on('resize', () => console.log('窗口尺寸变化'))
mainWindow.on('move', () => console.log('窗口移动'))

无边框窗口与自定义标题栏
设置 frame: false 后,可以自定义标题栏区域,并通过 CSS 与 JS 模拟拖拽:

.titlebar {
  -webkit-app-region: drag;
}

需要注意非拖拽区域(如按钮)应加上:

.titlebar {
  -webkit-app-region: drag;
}

窗口事件监听

mainWindow.on('close', (e) => {
  e.preventDefault()
  mainWindow.hide()
})

常用事件包括:

  • ready-to-show:页面加载完成,可安全显示。
  • focus / blur:窗口获得或失去焦点。
  • closed:窗口已被销毁。

加载页面内容

Electron 窗口可以加载本地 HTML 文件或远程网站。

加载本地 HTML 文件

mainWindow.loadFile('index.html')

加载远程 URL

mainWindow.loadURL('https://example.com')

使用前端框架
如果你使用 Vue、React 或 Angular 等现代框架,可以将构建后的静态文件加载到窗口:

mainWindow.loadFile(path.join(__dirname, 'dist/index.html'))

或者在开发模式下加载本地服务:

mainWindow.loadURL('http://localhost:5173')

窗口间通信

在多窗口应用中,可能需要在窗口间传递数据或控制行为。

父子窗口关系
可以在创建新窗口时指定 parent

let child = new BrowserWindow({
  parent: mainWindow,
  modal: true, // 模态窗口
  width: 400,
  height: 300,
})

父窗口关闭时,子窗口也会自动关闭。

多窗口数据共享
可以通过主进程作为中介,在不同渲染进程之间传递数据,也可使用全局对象或 ipcMain / ipcRenderer

窗口消息传递
主进程可直接操作目标窗口:

child.webContents.send('update-data', someData)

在子窗口中接收:

const { ipcRenderer } = require('electron')
ipcRenderer.on('update-data', (event, data) => {
  console.log(data)
})

界面优化

启动画面(Splash Screen)
在主窗口加载较慢时,可先显示一个轻量级启动窗口:

const splash = new BrowserWindow({ width: 400, height: 300, frame: false })
splash.loadFile('splash.html')

mainWindow.once('ready-to-show', () => {
  splash.close()
  mainWindow.show()
})

窗口显示优化
为防止白屏闪烁,常用技巧包括:

  • 使用 show: false 创建窗口,待加载完再 show()
  • 在渲染页面中设置背景色。
  • 预加载资源(图标、字体、样式)。

防止闪烁的技巧

  • 开启 GPU 加速或禁用硬件加速(视具体情况)。
  • 在 CSS 中避免复杂动画或渐变。
  • 使用双缓冲渲染,减少绘制抖动。

多窗口 Electron 应用(含启动页、主界面与子窗口)

目录结构

my-electron-app/
├── package.json
├── main.js             # 主进程入口
├── preload.js          # 预加载脚本
├── splash.html         # 启动画面
├── index.html          # 主窗口页面
├── child.html          # 子窗口页面
└── renderer.js         # 渲染进程逻辑

main.js(主进程)

实例

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

let mainWindow, splash, childWindow

function createSplash() {
  splash = new BrowserWindow({
    width: 400,
    height: 300,
    frame: false,
    transparent: true,
    alwaysOnTop: true,
  })
  splash.loadFile('splash.html')
}

function createMainWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    show: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  })

  mainWindow.loadFile('index.html')

  // 页面加载完成后再显示
  mainWindow.once('ready-to-show', () => {
    if (splash) splash.close()
    mainWindow.show()
  })

  mainWindow.on('closed', () => (mainWindow = null))
}

function createChildWindow() {
  childWindow = new BrowserWindow({
    parent: mainWindow,
    modal: true,
    width: 400,
    height: 300,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  })
  childWindow.loadFile('child.html')
}

// 进程间通信
ipcMain.on('open-child', () => {
  if (!childWindow) createChildWindow()
})

ipcMain.handle('get-app-info', () => {
  return {
    name: app.getName(),
    version: app.getVersion(),
  }
})

app.on('ready', () => {
  createSplash()
  setTimeout(() => createMainWindow(), 1500)
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

preload.js(预加载脚本)
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  openChild: () => ipcRenderer.send('open-child'),
  getAppInfo: () => ipcRenderer.invoke('get-app-info'),
})

index.html(主窗口)

实例

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>主窗口</title>
  <style>
    body { font-family: sans-serif; text-align: center; margin-top: 100px; }
    button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
  </style>
</head>
<body>
  <h1>欢迎使用 Electron 应用</h1>
  <p id="info"></p>
  <button id="open">打开子窗口</button>

  <script src="renderer.js"></script>
</body>
</html>

child.html(子窗口)

实例

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>子窗口</title>
  <style>
    body { font-family: sans-serif; text-align: center; margin-top: 100px; }
  </style>
</head>
<body>
  <h2>这是一个子窗口</h2>
  <p>可以接收来自主窗口的指令</p>
</body>
</html>

splash.html(启动画面)

实例

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>加载中...</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background: linear-gradient(135deg, #8EC5FC, #E0C3FC);
      font-family: sans-serif;
    }
    h1 {
      font-size: 28px;
      color: #333;
    }
  </style>
</head>
<body>
  <h1>应用启动中,请稍候...</h1>
</body>
</html>

renderer.js(渲染逻辑)

实例

const info = document.getElementById('info')
const btn = document.getElementById('open')

// 显示应用信息
window.electronAPI.getAppInfo().then((appInfo) => {
  info.textContent = `应用名称:${appInfo.name} | 版本:${appInfo.version}`
})

// 打开子窗口
btn.addEventListener('click', () => {
  window.electronAPI.openChild()
})

运行后流程如下:

  1. 启动时显示 启动画面splash.html)。
  2. 加载完成后自动关闭启动页,展示 主窗口
  3. 主窗口可通过按钮打开 子窗口,展示 IPC 通信功能。
  4. 主窗口使用了 Preload 安全暴露 API,防止直接访问 Node.js。
  5. 所有窗口都使用了 事件监听延迟显示 等优化策略,避免白屏与闪烁。