Skip to main content

夜间模式

¥Dark Mode

概述

¥Overview

自动更新原生界面

¥Automatically update the native interfaces

"原生接口" 包括文件选择器、窗口边框、对话框、上下文菜单等 - UI 来自操作系统而不是应用的任何内容。默认行为是从操作系统选择此自动主题。

¥"Native interfaces" include the file picker, window border, dialogs, context menus, and more - anything where the UI comes from your operating system and not from your app. The default behavior is to opt into this automatic theming from the OS.

自动更新你自己的界面

¥Automatically update your own interfaces

如果你的应用有自己的夜间模式,你应该与系统的夜间模式设置同步打开和关闭它。你可以使用 prefers-color-scheme CSS 媒体查询来完成此操作。

¥If your app has its own dark mode, you should toggle it on and off in sync with the system's dark mode setting. You can do this by using the prefers-color-scheme CSS media query.

手动更新你自己的界面

¥Manually update your own interfaces

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

¥If you want to manually switch between light/dark modes, you can do this by setting the desired mode in the themeSource property of the nativeTheme module. This property's value will be propagated to your Renderer process. Any CSS rules related to prefers-color-scheme will be updated accordingly.

macOS 设置

¥macOS settings

在 macOS 10.14 Mojave 中,Apple 为所有 macOS 电脑引入了新的 全系统夜间模式。如果你的 Electron 应用具有夜间模式,你可以使用 nativeTheme API 使其遵循系统范围的夜间模式设置。

¥In macOS 10.14 Mojave, Apple introduced a new system-wide dark mode for all macOS computers. If your Electron app has a dark mode, you can make it follow the system-wide dark mode setting using the nativeTheme api.

在 macOS 10.15 Catalina 中,Apple 为所有 macOS 电脑引入了新的 "automatic" 夜间模式选项。为了使 nativeTheme.shouldUseDarkColorsTray API 在 Catalina 上在此模式下正常工作,你需要使用 Electron >=7.0.0,或者在旧版本的 Info.plist 文件中将 NSRequiresAquaSystemAppearance 设置为 falseElectron PackagerElectron Forge 都有一个 darwinDarkModeSupport 选项,用于在应用构建期间自动执行 Info.plist 更改。

¥In macOS 10.15 Catalina, Apple introduced a new "automatic" dark mode option for all macOS computers. In order for the nativeTheme.shouldUseDarkColors and Tray APIs to work correctly in this mode on Catalina, you need to use Electron >=7.0.0, or set NSRequiresAquaSystemAppearance to false in your Info.plist file for older versions. Both Electron Packager and Electron Forge have a darwinDarkModeSupport option to automate the Info.plist changes during app build time.

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

¥If you wish to opt-out while using Electron > 8.0.0, you must set the NSRequiresAquaSystemAppearance key in the Info.plist file to true. Please note that Electron 8.0.0 and above will not let you opt-out of this theming, due to the use of the macOS 10.14 SDK.

示例

¥Example

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

¥This example demonstrates an Electron application that derives its theme colors from the nativeTheme. Additionally, it provides theme toggle and reset controls using IPC channels.

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()
}
})

这是如何运作的?

¥How does this work?

index.html 文件开始:

¥Starting with the index.html file:

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 文件:

¥And the styles.css file:

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> 元素背景和文本颜色。

¥The example renders an HTML page with a couple elements. The <strong id="theme-source"> element shows which theme is currently selected, and the two <button> elements are the controls. The CSS file uses the prefers-color-scheme media query to set the <body> element background and text colors.

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

¥The preload.js script adds a new API to the window object called darkMode. This API exposes two IPC channels to the renderer process, 'dark-mode:toggle' and 'dark-mode:system'. It also assigns two methods, toggle and system, which pass messages from the renderer to the main process.

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

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

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

¥Now the renderer process can communicate with the main process securely and perform the necessary mutations to the nativeTheme object.

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

¥The renderer.js file is responsible for controlling the <button> functionality.

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 方法。

¥Using addEventListener, the renderer.js file adds 'click' event listeners to each button element. Each event listener handler makes calls to the respective window.darkMode API methods.

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

¥Finally, the main.js file represents the main process and contains the actual 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 页面上按钮的单击事件。

¥The ipcMain.handle methods are how the main process responds to the click events from the buttons on the HTML page.

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

¥The 'dark-mode:toggle' IPC channel handler method checks the shouldUseDarkColors boolean property, sets the corresponding themeSource, and then returns the current shouldUseDarkColors property. Looking back on the renderer process event listener for this IPC channel, the return value from this handler is utilized to assign the correct text to the <strong id='theme-source'> element.

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

¥The 'dark-mode:system' IPC channel handler method assigns the string 'system' to the themeSource and returns nothing. This also corresponds with the relative renderer process event listener as the method is awaited with no return value expected.

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

¥Run the example using Electron Fiddle and then click the "Toggle Dark Mode" button; the app should start alternating between a light and dark background color.

Dark Mode