Electron 创建和管理窗口
窗口配置项、多窗口管理、父子窗口、无边框窗口与模态框。
常用配置
| Option | Effect |
|---|---|
width / height | 初始大小 |
minWidth / minHeight | 防止拖到不可用 |
frame: false | 自己画标题栏 |
transparent: true | 圆角、异形窗口 |
resizable | false 固定大小 |
alwaysOnTop | 悬浮窗 |
parent / modal | 子窗口跟随父窗口 |
show: false | 配合 ready-to-show 防白屏 |
创建窗口
const { BrowserWindow } = require('electron');
const path = require('node:path');
function createMainWindow() {
const win = new BrowserWindow({
width: 1200, height: 800,
minWidth: 900, minHeight: 600,
show: false,
backgroundColor: '#0f172a',
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
});
win.once('ready-to-show', () => win.show());
win.loadFile('index.html');
return win;
}多窗口管理
const windows = new Map();
function openWindow(id) {
if (windows.has(id)) {
const w = windows.get(id);
if (w.isMinimized()) w.restore();
w.focus();
return w;
}
const w = new BrowserWindow({ width: 800, height: 600, show: false });
w.loadFile(`pages/${id}.html`);
w.once('ready-to-show', () => w.show());
w.on('closed', () => windows.delete(id)); // 必须清理,否则引用泄漏
windows.set(id, w);
return w;
}Track windows in a Map and focus the existing one instead of opening another. Delete the reference on closed, or closed windows linger in memory and windows.size only grows.
模态窗口
function openSettings(parent) {
const dlg = new BrowserWindow({
width: 480, height: 360,
parent, // 跟随父窗口
modal: true, // 父窗口被禁用,必须先处理这个
resizable: false,
minimizable: false,
maximizable: false,
show: false
});
dlg.setMenuBarVisibility(false);
dlg.loadFile('settings.html');
dlg.once('ready-to-show', () => dlg.show());
}With modal: true clicks on the parent do nothing until the child closes. Good for mandatory confirmations; overuse makes the app feel frozen, so avoid it for everyday actions.
无边框窗口
/* 自绘标题栏:留出可拖拽区域 */
.titlebar {
height: 38px;
-webkit-app-region: drag; /* 整条可拖 */
background: #111827;
display: flex; align-items: center; padding: 0 12px;
}
.titlebar button {
-webkit-app-region: no-drag; /* 按钮不能拖,否则点不到 */
}<div class="titlebar">
<span>我的应用</span>
<button id="min">—</button>
<button id="max">□</button>
<button id="close">×</button>
</div>-webkit-app-region: drag makes a region draggable, but buttons inside must be marked no-drag or the drag logic swallows their clicks — the most common borderless-window bug.
app.getPath('userData') on close, read it when creating. Users have muscle memory for window geometry.窗口事件
win.on('close', (e) => {
// 有未保存内容时拦一下
if (hasUnsavedChanges) {
e.preventDefault();
const choice = dialog.showMessageBoxSync(win, {
type: 'warning',
buttons: ['取消', '不保存退出'],
defaultId: 0,
message: '有未保存的修改,确定退出?'
});
if (choice === 1) { hasUnsavedChanges = false; win.close(); }
}
});
win.on('closed', () => { mainWindow = null; });
win.on('focus', () => { /* 恢复轮询 */ });
win.on('blur', () => { /* 暂停轮询 */ });close can be preventDefaulted; closed means it is gone for good. Mixing them up gives you a double confirmation dialog or no interception at all.
常用配置
| 配置 | 作用 |
|---|---|
| 尺寸 | 初始大小 |
| 最小尺寸 | 防止拖到不可用 |
| 无边框 | 自己画标题栏 |
| 透明 | 圆角、异形窗口 |
| 可缩放 | false 固定大小 |
| 置顶 | 悬浮窗 |
| 父子/模态 | 子窗口跟随父窗口 |
| 延迟显示 | 配合 ready-to-show 防白屏 |
创建窗口
const { BrowserWindow } = require('electron');
const path = require('node:path');
function createMainWindow() {
const win = new BrowserWindow({
width: 1200, height: 800,
minWidth: 900, minHeight: 600,
show: false,
backgroundColor: '#0f172a',
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
});
win.once('ready-to-show', () => win.show());
win.loadFile('index.html');
return win;
}多窗口管理
const windows = new Map();
function openWindow(id) {
if (windows.has(id)) {
const w = windows.get(id);
if (w.isMinimized()) w.restore();
w.focus();
return w;
}
const w = new BrowserWindow({ width: 800, height: 600, show: false });
w.loadFile(`pages/${id}.html`);
w.once('ready-to-show', () => w.show());
w.on('closed', () => windows.delete(id)); // 必须清理,否则引用泄漏
windows.set(id, w);
return w;
}用 Map 记住窗口,重复打开时聚焦已有的,而不是再开一个。closed 事件里必须删掉引用——否则关掉的窗口对象一直留在内存里,windows.size 只增不减。
模态窗口
function openSettings(parent) {
const dlg = new BrowserWindow({
width: 480, height: 360,
parent, // 跟随父窗口
modal: true, // 父窗口被禁用,必须先处理这个
resizable: false,
minimizable: false,
maximizable: false,
show: false
});
dlg.setMenuBarVisibility(false);
dlg.loadFile('settings.html');
dlg.once('ready-to-show', () => dlg.show());
}modal: true 时用户点父窗口不会有任何反应,直到关掉子窗口。适合「必须确认」的场景;用多了会让用户觉得应用卡住,日常操作不要滥用。
无边框窗口
/* 自绘标题栏:留出可拖拽区域 */
.titlebar {
height: 38px;
-webkit-app-region: drag; /* 整条可拖 */
background: #111827;
display: flex; align-items: center; padding: 0 12px;
}
.titlebar button {
-webkit-app-region: no-drag; /* 按钮不能拖,否则点不到 */
}<div class="titlebar">
<span>我的应用</span>
<button id="min">—</button>
<button id="max">□</button>
<button id="close">×</button>
</div>-webkit-app-region: drag 让区域可拖动窗口,但里面的按钮必须标 no-drag,否则点击事件被拖动逻辑吃掉——这是无边框窗口最常见的 bug。
app.getPath('userData') 下的 JSON 里,在 close 事件里写、在创建时读。用户对窗口大小是有肌肉记忆的。窗口事件
win.on('close', (e) => {
// 有未保存内容时拦一下
if (hasUnsavedChanges) {
e.preventDefault();
const choice = dialog.showMessageBoxSync(win, {
type: 'warning',
buttons: ['取消', '不保存退出'],
defaultId: 0,
message: '有未保存的修改,确定退出?'
});
if (choice === 1) { hasUnsavedChanges = false; win.close(); }
}
});
win.on('closed', () => { mainWindow = null; });
win.on('focus', () => { /* 恢复轮询 */ });
win.on('blur', () => { /* 暂停轮询 */ });close 可以 preventDefault 拦下来,closed 表示已经关了、不可撤销。搞混这两个名字,会导致「确认框弹两次」或者拦不住。