メインコンテンツにスキップ

ダークモード

概要

ネイティブインターフェースを自動的に更新する

「ネイティブインターフェース」には、ファイルピッカー、ウィンドウの境界線、ダイアログ、コンテキストメニューなど、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ファイルでNSRequiresAquaSystemAppearancefalseに設定する必要があります。Electron PackagerElectron Forgeの両方に、アプリのビルド時にInfo.plistの変更を自動化するためのdarwinDarkModeSupportオプションがあります。

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">要素は、現在選択されているテーマを示し、2つの<button>要素はコントロールです。CSSファイルは、prefers-color-schemeメディアクエリを使用して、<body>要素の背景色とテキスト色を設定します。

preload.jsスクリプトは、darkModeという新しいAPIをwindowオブジェクトに追加します。このAPIは、レンダラープロセスに2つのIPCチャネル'dark-mode:toggle''dark-mode:system'を公開します。また、レンダラーからメインプロセスにメッセージを渡す2つのメソッド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'
})

addEventListenerを使用して、renderer.jsファイルは、各ボタン要素に'click'イベントリスナーを追加します。各イベントリスナーハンドラーは、それぞれのwindow.darkModeAPIメソッドを呼び出します。

最後に、main.jsファイルはメインプロセスを表し、実際のnativeThemeAPIを含みます。

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