ダークモード
概要
ネイティブインターフェースを自動的に更新する
「ネイティブインターフェース」には、ファイルピッカー、ウィンドウの境界線、ダイアログ、コンテキストメニューなど、OSから提供されるUIでアプリからではないものが含まれます。デフォルトの動作は、OSからのこの自動テーマ設定を有効にすることです。
独自のインターフェースを自動的に更新する
アプリに独自のダークモードがある場合は、システムのダークモード設定と同期してオンとオフを切り替える必要があります。これは、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コンピューターに新しい「自動」ダークモードオプションを導入しました。CatalinaでこのモードでnativeTheme.shouldUseDarkColors
およびTray
APIを正しく機能させるには、Electron >=7.0.0
を使用するか、古いバージョンの場合はInfo.plist
ファイルでNSRequiresAquaSystemAppearance
をfalse
に設定する必要があります。Electron PackagerとElectron Forgeの両方に、アプリのビルド時にInfo.plist
の変更を自動化するためのdarwinDarkModeSupport
オプションがあります。
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">
要素は、現在選択されているテーマを示し、2つの<button>
要素はコントロールです。CSSファイルは、prefers-color-scheme
メディアクエリを使用して、<body>
要素の背景色とテキスト色を設定します。
preload.js
スクリプトは、darkMode
という新しいAPIをwindow
オブジェクトに追加します。このAPIは、レンダラープロセスに2つのIPCチャネル'dark-mode:toggle'
と'dark-mode:system'
を公開します。また、レンダラーからメインプロセスにメッセージを渡す2つのメソッド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を使用して例を実行し、「ダークモードの切り替え」ボタンをクリックします。アプリは、明るい背景色と暗い背景色を交互に表示し始めるはずです。