Dark Mode暗模式
Overview概述
Automatically update the native interfaces自动更新本机接口
"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. “本机界面”包括文件选择器、窗口边框、对话框、上下文菜单等-任何UI来自操作系统而非应用程序的界面。The default behavior is to opt into this automatic theming from the OS.默认行为是从操作系统选择自动主题化。
Automatically update your own interfaces自动更新您自己的界面
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 [prefer-color-scheme] CSS media query.您可以使用[Prefere color scheme]CSS媒体查询来实现这一点。
Manually update your own interfaces手动更新您自己的界面
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. nativeTheme
模块的themeSource属性中设置所需模式来实现。This property's value will be propagated to your Renderer process. 此属性的值将传播到渲染器进程。Any CSS rules related to 任何与首选颜色方案相关的CSS规则都将相应更新。prefers-color-scheme
will be updated accordingly.
macOS settingsmacOS设置
In macOS 10.14 Mojave, Apple introduced a new system-wide dark mode for all macOS computers. 在macOS 10.14 Mojave中,苹果为所有macOS计算机引入了新的全系统暗模式。If your Electron app has a dark mode, you can make it follow the system-wide dark mode setting using the 如果您的Electron应用程序具有暗模式,您可以使用nativeTheme api使其遵循系统范围的暗模式设置。nativeTheme
api.
In macOS 10.15 Catalina, Apple introduced a new "automatic" dark mode option for all macOS computers. 在macOS 10.15 Catalina中,苹果为所有macOS计算机引入了新的“自动”暗模式选项。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. nativeTheme.shouldUseDarkColors
和Tray
API在此模式下在Catalina上正确工作,您需要使用Electron>=7.0.0
,或者在旧版本的Info.plist
文件中将NSRequiresAquaSystemAppearance
设置为false
。Both Electron Packager and Electron Forge have a Electron Packager和Electron Forge都有一个darwinDarkModeSupport
option to automate the Info.plist
changes during app build time.darwinDarkModeSupport
选项,可以在应用程序构建期间自动更改Info.plist
。
If you wish to opt-out while using Electron > 8.0.0, you must set the 如果希望在使用Electron>8.0.0时选择退出,则必须将NSRequiresAquaSystemAppearance
key in the Info.plist
file to true
. Info.plist
文件中的NSRequiresAquaSystemAppearance
键设置为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.请注意,由于使用了macOS 10.14 SDK,Electron 8.0.0及以上版本不允许您选择退出此主题。
Example示例
This example demonstrates an Electron application that derives its theme colors from the 此示例演示了一个从nativeTheme
. nativeTheme
派生主题颜色的Electron应用程序。Additionally, it provides theme toggle and reset controls using IPC channels.此外,它还提供使用IPC通道的主题切换和重置控件。
- main.js
- preload.js
- index.html
- renderer.js
- styles.css
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
const path = require('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')
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>
</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'
})
@media (prefers-color-scheme: dark) {
body { background: #333; color: white; }
}
@media (prefers-color-scheme: light) {
body { background: #ddd; color: black; }
}
How does this work?这是如何起作用的?
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>
</body>
</html>
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; }
}
The example renders an HTML page with a couple elements. 该示例呈现了一个包含两个元素的HTML页面。The <strong id="theme-source">
element shows which theme is currently selected, and the two <button>
elements are the controls. <strong id="theme-source">
元素显示当前选择的主题,两个<button>
元素是控件。The CSS file uses the prefers-color-scheme media query to set the CSS文件使用首选颜色方案媒体查询来设置<body>
element background and text colors.<body>
元素背景和文本颜色。
The preload.js
script adds a new API to the window
object called darkMode
. preload.js
脚本向名为darkMode
的window
对象添加了一个新的API。This API exposes two IPC channels to the renderer process, 该API向渲染器进程公开了两个IPC通道'dark-mode:toggle'
and 'dark-mode:system'
. 'dark-mode:toggle'
和'dark-mode:system'
。It also assigns two methods, 它还分配了两个方法:toggle
and system
, which pass messages from the renderer to the main process.toggle
和system
,它们将消息从渲染器传递到主进程。
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('darkMode', {
toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
system: () => ipcRenderer.invoke('dark-mode:system')
})
Now the renderer process can communicate with the main process securely and perform the necessary mutations to the 现在,渲染器进程可以安全地与主进程通信,并对nativeTheme对象执行必要的修改。nativeTheme
object.
The renderer.js
file is responsible for controlling the <button>
functionality.renderer.js
文件负责控制<button>
功能。
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'
})
Using 使用addEventListener
, the renderer.js
file adds 'click'
event listeners to each button element. addEventListener
,renderer.js
文件将'click'
事件监听器添加到每个按钮元素。Each event listener handler makes calls to the respective 每个事件侦听器处理程序都调用相应的window.darkMode
API methods.window.darkMode
API方法。
Finally, the 最后,main.js
file represents the main process and contains the actual nativeTheme
API.main.js
文件表示主进程并包含实际的nativeTheme
API。
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
const path = require('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()
}
})
The ipcMain.handle
methods are how the main process responds to the click events from the buttons on the HTML page.ipcMain.handle
方法是主进程如何响应HTML页面上按钮的点击事件。
The 'dark-mode:toggle'
IPC channel handler method checks the shouldUseDarkColors
boolean property, sets the corresponding themeSource
, and then returns the current shouldUseDarkColors
property. 'dark-mode:toggle'
IPC通道处理程序方法检查shouldUseDarkColors
布尔属性,设置相应的themeSource
,然后返回当前的shouldUseDarkColors
属性。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 回顾此IPC通道的渲染器进程事件监听器,该处理程序的返回值用于将正确的文本分配给<strong id='theme-source'>
element.<strong id='theme-source'>
元素。
The 'dark-mode:system'
IPC channel handler method assigns the string 'system'
to the themeSource
and returns nothing. 'dark-mode:system'
IPC通道处理程序方法将字符串'system'
分配给themeSource,但不返回任何内容。This also corresponds with the relative renderer process event listener as the method is awaited with no return value expected.这也对应于相对渲染器进程事件侦听器,因为该方法在等待时没有预期的返回值。
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.使用Electron Fiddle运行示例,然后单击“切换暗模式”按钮;应用程序应开始在浅色和深色背景色之间交替。