人人都会AI编程

32.2 系统体验适配:深色模式、DPI 缩放、高分辨率适配

更新时间:2026-07-11

一个真正好用的桌面应用,不应该让用户觉得“它和我的系统格格不入”。深色模式、高分屏清晰度、字体和窗口的缩放适配,这些细节直接决定了你的应用在第一眼看起来是“粗糙的网页壳”还是“精致的原生程序”。本节会给出最直接、最实用的方案,让你用最少的时间把这些体验打磨到位。


29.2.1 深色模式:让应用跟随系统明暗

现在几乎所有主流操作系统都提供了深色/浅色主题切换,Electron 提供了 nativeTheme 模块来无缝对接这一特性。

检测并响应系统主题变化

你的应用需要知道当前系统处于何种颜色模式,并在切换时立即更新界面。最简单的方法是在主进程监听 nativeThemeupdated 事件,然后把结果通过 IPC 发给渲染进程:

// 主进程 (main)
const { app, BrowserWindow, nativeTheme, ipcMain } = require('electron');

function sendTheme(win) {
  win.webContents.send('theme-changed', nativeTheme.shouldUseDarkColors);
}

app.whenReady().then(() => {
  const win = new BrowserWindow({ /* ... */ });
  sendTheme(win);
  nativeTheme.on('updated', () => sendTheme(win));
});

在渲染进程里,收到消息后就可以给 body 添加或移除一个 dark 类,CSS 规则会跟着生效:

// 渲染进程 (renderer)
ipcRenderer.on('theme-changed', (event, isDark) => {
  document.body.classList.toggle('dark', isDark);
});
/* 浅色模式风格 */
body { background: #fff; color: #333; }
/* 深色模式覆盖 */
body.dark { background: #1e1e1e; color: #ccc; }

更现代的做法:如果你的 Electron 版本 ≥ 13,可以直接在渲染进程使用 CSS 媒体查询,完全不需要 IPC:
@@SNAPSHOT_BLOCK_3@@
这种方式更轻量,前提是你的 UI 不包含需要 JS 控制的高级主题切换逻辑。

允许用户手动切换主题

除了跟随系统,很多应用允许用户在设置中固定选择“浅色”、“深色”或“跟随系统”。你只需要在主进程设置 nativeTheme.themeSource

// 固定深色模式
nativeTheme.themeSource = 'dark';
// 固定浅色模式
nativeTheme.themeSource = 'light';
// 恢复跟随系统
nativeTheme.themeSource = 'system';

设置完这个值之后,nativeTheme.shouldUseDarkColors 会立刻更新,同时 updated 事件也会触发,你的渲染进程也会收到通知。这样就能实现应用级的个性化主题设置。

让标题栏和菜单也跟上

Electron 的 BrowserWindow 有一个 titleBarStyle 选项,配合系统深色模式可以让窗口标题栏变成暗色。在 macOS 上,如果你需要自定义标题栏,可以设置 titleBarStyle: 'hiddenInset' 然后用 CSS 模拟标题栏;Windows 和 Linux 上一般使用默认原生标题栏,它本身就会响应系统主题。

更简单的做法:创建窗口时传入 backgroundColor 参数,让标题栏颜色与界面背景融合,例如:

const win = new BrowserWindow({
  backgroundColor: '#1e1e1e',       // 深色背景避免白闪
  titleBarStyle: 'hidden',          // macOS隐藏标题栏
  // ...
});

注意,如果你完全隐藏标题栏并在渲染进程里自己画关闭按钮,一定要使用 windowControlsOverlay API(在支持的平台上)或确保拖拽区域正常工作。对于追求原生感的应用,建议保留系统标题栏适配,除非有特殊设计需求。


29.2.2 DPI 缩放:告别模糊与错位

很多桌面应用在 4K 显示器上打开的那一刻就“原形毕露”:窗口要么小得像蚂蚁,要么模糊得像隔了层毛玻璃。造成这个问题的原因是应用没有正确处理不同的 DPI 设置。

Electron 默认做了哪些事

从 Electron 12 开始,在 Windows 上框架会自动启用 per-monitor V2 DPI 感知,这意味着窗口在不同缩放比例的显示器间拖动时,Electron 会自动适配系统缩放。在 macOS 上,原生就支持 Retina 渲染。理论上你的网页内容会按照系统设定的缩放比例渲染,不需要额外调整。

但实际情况是,很多开发者会写出一些“固定像素”的界面,比如 width: 1200px; height: 800px,在 200% 缩放的屏幕上,这些固定值就会显得过小。或者使用了 Canvas/图片但没有针对 devicePixelRatio 做处理,导致绘制模糊。

基础做法:让布局自适应

  • 尽量使用 rem%vwvh 等相对单位,让界面跟随窗口大小和缩放比例自然伸缩。
  • 使用 window.innerWidth / window.innerHeight 判断可用区域,在 CSS 里写响应式断点,而不是假设显示器一定多大。
  • 给你的应用窗口一个合理的默认大小,但允许用户自由拉伸,并用 win.setMinimumSize() 防止被缩到不可用。

图片和 Canvas 的清晰化处理

对于 <img> 标签,最保险的做法是使用矢量 SVG,或者为关键位图提供 @2x@3x 版本,并通过 <picture> 或 srcset 按设备像素比加载:

<img src="icon.png" srcset="icon@2x.png 2x, icon@3x.png 3x" />

Canvas 则需要根据 devicePixelRatio 调整画布实际分辨率和 CSS 显示尺寸:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const ratio = window.devicePixelRatio || 1;

canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.style.width = canvas.offsetWidth + 'px';
canvas.style.height = canvas.offsetHeight + 'px';
ctx.scale(ratio, ratio);
// 之后所有绘制操作会以逻辑像素坐标进行,渲染结果清晰

处理系统字体大小

Windows 允许用户单独调整文字缩放比例(例如 125%),这会反映到 Electron 的 systemPreferences 中。如果你希望应用的字体大小跟随系统文字缩放,可以这样获取:

// 主进程
const { systemPreferences } = require('electron');
const textScaleFactor = systemPreferences.getTextScaleFactor?.() || 1;
// 将此值传给渲染进程,设置 html { font-size: calc(16px * scaleFactor); }

但大多数应用默认跟随系统的整体 DPI 缩放已经足够,除非你的应用需要严格匹配系统字体大小设置。

特殊场景:强制固定缩放比例

如果因为某些遗留 UI 库的原因,你希望完全关闭系统的 DPI 缩放,可以用命令行参数强制将网页内容锁定在 100% 缩放(极少推荐使用,仅作为最后手段):

app.commandLine.appendSwitch('force-device-scale-factor', '1');

这条命令会让所有内容无视系统缩放,按 1:1 渲染,可能导致字体和界面在高分屏上极小,所以务必谨慎。


29.2.3 高分辨率适配:每个像素都不能糊

“高分辨率适配”和 DPI 缩放是一体两面:DPI 保证大小合适,高分辨率保证清晰。这一节专门补一些容易被忽略的细节。

应用图标要提供全尺寸

Electron 允许为不同尺寸准备图标,打包时指定 icon 字段即可。但是为了在所有场合(任务栏、系统托盘、关于对话框)都清晰,你需要准备:

  • macOS:icon.icns,应包含 16×16 到 512×512 甚至 1024×1024 的尺寸。
  • Windows:icon.ico,应打包 16×16、32×32、48×48、256×256 的 PNG 图。
  • Linux:使用 PNG 并放置在合适路径,或者由 electron-builder 从指定 PNG 自动生成多尺寸图标。

为了省心,推荐使用 electron-icon-builder 或类似工具从一张 1024×1024 的 PNG 自动生成全平台需要的图标资源。

菜单和工具栏里的图标

如果你的应用使用了原生菜单或系统托盘,nativeImage 支持自动选择高分辨率版本。你只需提供命名规范的图片文件,例如:

  • icon.png(基本图)
  • icon@2x.png(两倍分辨率)
  • icon@3x.png(三倍分辨率)

然后直接用:

const icon = nativeImage.createFromPath(path.join(__dirname, 'icon.png'));
tray = new Tray(icon);

Electron 会根据当前显示器的缩放比例自动选择合适的版本,无需你手动判断。

视频和动画的清晰度

如果你的应用嵌入了 <video> 播放器,确保源文件分辨率至少不低于窗口可显示的最大物理像素。例如在 4K 屏幕上播放 1080p 视频,需要浏览器拉伸,会显得模糊。若条件允许,优先提供更高清资源或使用自适应码率。

测试清单

完成适配后,请花 10 分钟执行以下操作,确保问题清零:

  1. 在 Windows 设置里分别将缩放设置为 100%、125%、150%、200%,打开应用检查:界面元素是否清晰?窗口是否越界?文本是否溢出?
  2. 在 macOS 系统偏好设置的“显示器”中切换“缩放”分辨率,测试应用跟随情况。
  3. 把你的应用窗口从一块 100% 缩放的显示器拖到一块 200% 缩放的显示器上,观察窗口内容是否立即重新渲染清晰(Windows 下测试 per-monitor DPI)。
  4. 打开系统深色模式,确认应用主题和原生控件(如滚动条、菜单)都变成了暗色风格。
  5. 在不同分辨率的虚拟机上测试 Linux 的适配(特别是 Gnome 的缩放设定)。

这 10 分钟的测试,能让 90% 的用户在安装你的应用时,发出“嗯,做得挺专业”的第一印象。把细节做全,你的 Electron 应用就不会轻易被归类为“网页套壳玩具”。