夜间模式
¥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.shouldUseDarkColors
和 Tray
API 在 Catalina 上在此模式下正常工作,你需要使用 Electron >=7.0.0
,或者在旧版本的 Info.plist
文件中将 NSRequiresAquaSystemAppearance
设置为 false
。Electron Packager 和 Electron 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.
- main.js
- preload.js
- index.html
- renderer.js
- styles.css
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()
}
})
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('darkMode', {
toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
system: () => ipcRenderer.invoke('dark-mode:system')
})
<!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>
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'
})
:root {
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
body { background: #333; color: white; }
}
@media (prefers-color-scheme: light) {
body { background: #ddd; color: black; }
}
这是如何运作的?
¥How does this work?
从 index.html
文件开始:
¥Starting with the index.html
file:
<!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:
@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'
。它还分配了两个方法,toggle
和 system
,它们将消息从渲染器传递到主进程。
¥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.
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.
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'
})
使用 addEventListener
,renderer.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.