跳至主要內容

深色模式

概述

自動更新原生介面

「原生介面」包含檔案選擇器、視窗邊框、對話方塊、內容選單等等,任何使用者介面來自您的作業系統而非您的應用程式。預設行為是選擇啟用來自作業系統的自動主題設定。

自動更新您自己的介面

如果您的應用程式有自己的深色模式,您應該與系統的深色模式設定同步開啟和關閉它。您可以使用 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.shouldUseDarkColorsTray API 在 Catalina 上的此模式下正常運作,您需要在較舊的版本中使用 Electron >=7.0.0,或在您的 Info.plist 檔案中將 NSRequiresAquaSystemAppearance 設定為 falseElectron PackagerElectron Forge 都有一個 darwinDarkModeSupport 選項,可在應用程式建置期間自動執行 Info.plist 的變更。

如果您在使用 Electron > 8.0.0 時想要選擇退出,您必須在 Info.plist 檔案中將 NSRequiresAquaSystemAppearance 鍵設定為 true。請注意,由於使用 macOS 10.14 SDK,Electron 8.0.0 及以上版本不允許您選擇退出此主題設定。

範例

此範例示範一個從 nativeTheme 衍生其主題顏色的 Electron 應用程式。此外,它還提供使用 IPC 通道的切換主題和重設控制。

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

這是如何運作的?

index.html 檔案開始

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 檔案

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 物件中新增一個名為 darkMode 的 API。此 API 會向渲染器進程公開兩個 IPC 通道,'dark-mode:toggle''dark-mode:system'。它還會指定兩個方法,togglesystem,這些方法會將訊息從渲染器傳遞到主要進程。

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

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

現在,渲染器進程可以安全地與主要進程通訊,並對 nativeTheme 物件執行必要的變更。

renderer.js 檔案負責控制 <button> 的功能。

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

最後,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 執行此範例,然後按一下「切換深色模式」按鈕,應用程式應該會開始在淺色和深色背景顏色之間交替。

Dark Mode