跳到主要内容

窗口状态记忆(位置/尺寸)如何实现?多显示器场景有什么坑?

考察点​

  • 窗口状态的保存与恢复时机
  • 多显示器、分辨率变化下的健壮性处理

参考答案​

1. 基本实现​

const Store = require('electron-store');
const store = new Store();

function createWindow() {
// 读取上次保存的窗口状态
const state = store.get('windowState', {
width: 1200,
height: 800,
// x/y 首次启动时没有,让系统居中
});

const win = new BrowserWindow({
width: state.width,
height: state.height,
x: state.x,
y: state.y,
show: false,
});

if (state.isMaximized) win.maximize();

// 监听状态变化,防抖保存
const saveState = debounce(() => {
if (win.isDestroyed()) return;
const isMaximized = win.isMaximized();
store.set('windowState', {
// 最大化时不记录 bounds(拿到的是最大化后的尺寸)
...(isMaximized ? {} : win.getNormalBounds()),
isMaximized,
});
}, 500);

win.on('resize', saveState);
win.on('move', saveState);
win.on('maximize', saveState);
win.on('unmaximize', saveState);
win.on('close', saveState);

return win;
}

2. 三个关键细节​

(1)最大化时保存 getNormalBounds() 而不是 getBounds() getBounds() 在最大化时返回的是占满屏幕的尺寸,直接保存会导致还原后窗口尺寸错误。

(2)恢复位置前校验显示器是否还存在 用户上次在外接显示器关闭窗口,下次拔掉显示器启动 → 窗口会出现在"看不见的区域",这是多显示器场景最经典的 bug:

const { screen } = require('electron');

function isVisibleOnAnyDisplay(bounds) {
// 检查窗口位置是否与任一显示器工作区有交集
return screen.getAllDisplays().some((display) => {
const area = display.workArea;
return (
bounds.x < area.x + area.width &&
bounds.x + bounds.width > area.x &&
bounds.y < area.y + area.height &&
bounds.y + bounds.height > area.y
);
});
}

// 恢复时校验,不可见则丢弃 x/y 让系统居中
if (state.x !== undefined && !isVisibleOnAnyDisplay(state)) {
delete state.x;
delete state.y;
}

(3)保存时机

  • resize/move 事件高频触发,必须防抖(500ms 左右),否则频繁写磁盘
  • close 时最后保存一次兜底

3. 进阶:按显示器记忆​

更精细的做法是同时记录显示器 ID(screen.getDisplayMatching(bounds).id),恢复时优先放回原来的显示器。注意显示器 ID 在系统重连后可能变化,只能作为参考。

4. 现成方案​

electron-window-state 库封装了上述大部分逻辑,但理解原理后在面试中讲自己实现更加分——这个库多年未大更新,且多显示器校验仍需自己补。

面试话术​

状态记忆本身不难,难点在边界:最大化时存 normalBounds、移动/缩放防抖落盘、恢复前校验目标区域是否还在某个已连接显示器内。第三条漏掉就是"窗口消失了"的线上投诉。

常见追问​

  • 状态存哪? electron-store(JSON 文件)足够;存主进程内存则重启丢失。
  • 多窗口各自记忆? 按窗口业务 key 分别存 windowState.${key}。
  • DPI 变化(4K 屏换 1080P)要处理吗? 要,bounds 是 DIP(逻辑像素),跨 DPI 移动时尺寸可能变化,恢复时也可加最小尺寸保护。