跳转到主要内容

深色模式

概述

自动更新原生界面

“原生界面”包括文件选择器、窗口边框、对话框、上下文菜单等 - 任何 UI 来自您的操作系统而不是您的应用程序的地方。默认行为是选择启用操作系统中的这种自动主题设置。

自动更新您自己的界面

如果您的应用程序有自己的深色模式,您应该与系统的深色模式设置同步地切换它。您可以使用 prefers-color-scheme CSS 媒体查询来做到这一点。

手动更新您自己的界面

如果您想手动在浅色/深色模式之间切换,您可以通过在 nativeTheme 模块的 themeSource 属性中设置所需的模式来实现。此属性的值将传播到您的渲染器进程。任何与 prefers-color-scheme 相关的 CSS 规则都将相应更新。

macOS 设置

在 macOS 10.14 Mojave 中,Apple 为所有 macOS 计算机引入了新的系统范围的深色模式。如果您的 Electron 应用程序有深色模式,您可以使用nativeTheme API使其遵循系统范围的深色模式设置。

在 macOS 10.15 Catalina 中,Apple 为所有 macOS 计算机引入了新的“自动”深色模式选项。为了使 nativeTheme.shouldUseDarkColorsTray API 在 Catalina 上的此模式下正常工作,您需要使用 Electron >=7.0.0,或者在您的 Info.plist 文件中将 NSRequiresAquaSystemAppearance 设置为 false 用于较旧的版本。Electron PackagerElectron Forge都有一个 darwinDarkModeSupport 选项,可以在应用程序构建时自动更改 Info.plist

如果您希望在使用 Electron > 8.0.0 时选择退出,您必须在 Info.plist 文件中将 NSRequiresAquaSystemAppearance 键设置为 true。请注意,由于使用了 macOS 10.14 SDK,Electron 8.0.0 及更高版本将不允许您选择退出此主题设置。

示例

此示例演示了一个 Electron 应用程序,该应用程序从 nativeTheme 派生其主题颜色。此外,它还提供使用 IPC 通道的主题切换和重置控件。

const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron/main')
const path = require('node:path')

function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

win.loadFile('index.html')
}

ipcMain.handle('dark-mode:toggle', () => {
if (nativeTheme.shouldUseDarkColors) {
nativeTheme.themeSource = 'light'
} else {
nativeTheme.themeSource = 'dark'
}
return nativeTheme.shouldUseDarkColors
})

ipcMain.handle('dark-mode:system', () => {
nativeTheme.themeSource = 'system'
})

app.whenReady().then(() => {
createWindow()

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})

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

它是如何工作的?

index.html 文件开始

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<link rel="stylesheet" type="text/css" href="./styles.css">
</head>
<body>
<h1>Hello World!</h1>
<p>Current theme source: <strong id="theme-source">System</strong></p>

<button id="toggle-dark-mode">Toggle Dark Mode</button>
<button id="reset-to-system">Reset to System Theme</button>

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

以及 styles.css 文件

styles.css
@media (prefers-color-scheme: dark) {
body { background: #333; color: white; }
}

@media (prefers-color-scheme: light) {
body { background: #ddd; color: black; }
}

该示例呈现一个包含几个元素的 HTML 页面。 <strong id="theme-source"> 元素显示当前选择的主题,而两个 <button> 元素是控件。 CSS 文件使用 prefers-color-scheme 媒体查询来设置 <body> 元素的背景和文本颜色。

preload.js 脚本向 window 对象添加了一个名为 darkMode 的新 API。 此 API 向渲染器进程公开两个 IPC 通道,'dark-mode:toggle''dark-mode:system'。 它还分配了两个方法,togglesystem,它们将消息从渲染器传递到主进程。

preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('darkMode', {
toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
system: () => ipcRenderer.invoke('dark-mode:system')
})

现在,渲染器进程可以安全地与主进程通信,并对 nativeTheme 对象执行必要的更改。

renderer.js 文件负责控制 <button> 功能。

renderer.js
document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
const isDarkMode = await window.darkMode.toggle()
document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
})

document.getElementById('reset-to-system').addEventListener('click', async () => {
await window.darkMode.system()
document.getElementById('theme-source').innerHTML = 'System'
})

使用 addEventListenerrenderer.js 文件向每个按钮元素添加 'click' 事件监听器。 每个事件监听器处理程序都会调用各自的 window.darkMode API 方法。

最后,main.js 文件表示主进程,并包含实际的 nativeTheme API。

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

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

win.loadFile('index.html')

ipcMain.handle('dark-mode:toggle', () => {
if (nativeTheme.shouldUseDarkColors) {
nativeTheme.themeSource = 'light'
} else {
nativeTheme.themeSource = 'dark'
}
return nativeTheme.shouldUseDarkColors
})

ipcMain.handle('dark-mode:system', () => {
nativeTheme.themeSource = 'system'
})
}

app.whenReady().then(() => {
createWindow()

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})

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

ipcMain.handle 方法是主进程如何响应 HTML 页面上按钮的单击事件。

'dark-mode:toggle' IPC 通道处理程序方法检查 shouldUseDarkColors 布尔属性,设置相应的 themeSource,然后返回当前的 shouldUseDarkColors 属性。 回顾此 IPC 通道的渲染器进程事件监听器,此处理程序的返回值用于将正确的文本分配给 <strong id='theme-source'> 元素。

'dark-mode:system' IPC 通道处理程序方法将字符串 'system' 分配给 themeSource 并且不返回任何内容。 这也与相关的渲染器进程事件监听器相对应,因为该方法被等待,并且不期望返回值。

使用 Electron Fiddle 运行示例,然后单击“切换深色模式”按钮; 应用程序应该开始在浅色和深色背景颜色之间交替。

Dark Mode