夜间模式
🌐 Dark Mode
概述
🌐 Overview
自动更新原生界面
🌐 Automatically update the native interfaces
“原生界面”包括文件选择器、窗口边框、对话框、上下文菜单等——任何界面元素都是由你的操作系统提供,而不是你的应用提供的。默认行为是自动采用操作系统的主题风格。
自动更新你自己的界面
🌐 Automatically update your own interfaces
如果你的应用有自己的夜间模式,你应该与系统的夜间模式设置同步开启和关闭。你可以使用首选颜色方案 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 属性中设置所需模式来实现。该属性的值会传递到你的 Renderer 进程。与 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 中,苹果为所有 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 电脑引入了一个新的“自动”暗黑模式选项。为了让 nativeTheme.shouldUseDarkColors 和 Tray API 在 Catalina 中的此模式下正常工作,你需要使用 Electron >=7.0.0,或者在旧版本中将 NSRequiresAquaSystemAppearance 设置为 false,并放在你的 Info.plist 文件中。Electron 打包工具 和 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
此示例演示了一个 Electron 应用,该应用的主题颜色来源于 nativeTheme。此外,它还通过 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 对象添加了一个新的 API,称为 darkMode。这个 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,并且不返回任何值。这也对应于相关的渲染器进程事件监听器,因为该方法是以不期望返回值的方式被 await 的。
🌐 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.
