暗黑模式
概述
自动更新原生界面
"原生界面" 包括文件选择器、窗口边框、对话框、上下文菜单等等 - 任何来自操作系统的 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.shouldUseDarkColors
和 Tray
API 在 Catalina 上的这种模式下正常工作,你需要使用 Electron >=7.0.0
,或者在你的 Info.plist
文件中为旧版本设置 NSRequiresAquaSystemAppearance
为 false
。 Electron Packager 和 Electron Forge 都有一个 darwinDarkModeSupport
选项 来在应用程序构建时自动执行 Info.plist
的更改。
如果你想在使用 Electron > 8.0.0 时选择退出,你必须将 Info.plist
文件中的 NSRequiresAquaSystemAppearance
键设置为 true
。请注意,由于使用了 macOS 10.14 SDK,Electron 8.0.0 及更高版本不允许你选择退出这种主题设置。
示例
此示例演示了一个从 nativeTheme
中获取主题颜色的 Electron 应用程序。此外,它还使用 IPC 通道提供了主题切换和重置控件。
- 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; }
}
它是如何工作的?
从 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
文件
@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
对象添加了一个新的 API,称为 darkMode
。此 API 向渲染器进程暴露了两个 IPC 通道,'dark-mode:toggle'
和 'dark-mode:system'
。它还分配了两个方法,toggle
和 system
,它们将消息从渲染器进程传递到主进程。
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('darkMode', {
toggle: () => ipcRenderer.invoke('dark-mode:toggle'),
system: () => ipcRenderer.invoke('dark-mode:system')
})
现在,渲染器进程可以安全地与主进程通信,并对 nativeTheme
对象进行必要的修改。
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'
})
使用 addEventListener
,renderer.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 运行该示例,然后点击 "切换暗黑模式" 按钮;该应用应该开始在亮色和暗色背景颜色之间交替。