Electron で、macOS アプリケーションを開発するにあたって、一考するべきものとして「ダークテーマ」の切り替え実装でしょう。Electron には、macOS のシステムとしてライト/ダークのどちらが設定されているか?を知る方法、またユーザーによってリアルタイムに macOS の「外観」が変更されたときのイベント発火も用意されています。
macOS のダークモードのサポート - Electron ドキュメント
https://www.electronjs.org/docs/tutorial/mojave-dark-mode-guide
ただ、「どのようなダーク(またはライト)モードにするか?」という部分は、開発者自身で考えてデザイン、変更するコードを書かねばなりません。
具体的な実行イメージとしては、ダークモードへ変更されたとき、HTML で構成された UI は、適用された CSS ファイルを切り替える感じなのだろうかと想像しますが、複数のデザインを切り替えるならともかく、「ライトモード」と「ダークモード」の二種類なら、実際2ファイルの CSS を用意するよりも、後述の「CSS 変数」を使う方が、後のデザイン変更や微調整などのメンテナンスが容易になります。
サンプルアプリケーション
まずは、この記事を書くにあたって(というよりテストするにあたって)サンプルアプリケーションを作成しました。
hibara/Erectron-Dark-Mode-Test - GitHub
https://github.com/hibara/Erectron-Dark-Mode-Test
node_modules
は、アップロードされておりません。GitHub から pull
してきたら、カレントディレクトリーで、npm install
とコマンドを入力し、インストールしてからビルドしてください。
ライトモード | ダークモード |
---|---|
参考にしたのは、このサイトです。
https://dev.to/ananyaneogi/create-a-dark-light-mode-switch-with-css-variables-34l8
レイアウトは、これをベースに改良しています。
FLAT SIDE MENU NAVIGATION - CodePen
https://codepen.io/envoy47/pen/pOzmKa
実行ファイル
わざわざビルドする必要もなく、アプリケーションの動作のみを確認できれば良いという方は、同様に GitHub の以下のページからアプリのみをダウンロードしてお試しください。現在のところは、macOS版のみのビルド配布となっております。
ErectronDarkModeTest-darwin-x64.zip
https://github.com/hibara/Erectron-Dark-Mode-Test/releases
UI テーマは、CSS ファイル切り替えるのではなく「CSS 変数」を使うと便利
「CSS 変数」だけでなく、「カスタムプロパティ」や「カスケード変数」と呼ばれることもあるそうですが、ほとんどのモダンブラウザで使用可能です。
https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties
もちろん、Electron はモダンブラウザである、Chromium が使われているので問題ありません。
たとえば、--main-color: black;
によって設定し、 var() 関数(たとえば、color: var(--main-color);
)を使ってアクセスします。
今回のサンプルアプリケーションでは、スタイルシート冒頭にマテリアルデザインを意識した、ベースとなるグラデーションを定義しました。
:root {
--base-color-00: #333333;
--base-color-01: #494949;
--base-color-02: #606060;
--base-color-03: #777777;
--base-color-04: #8d8d8d;
--base-color-05: #a4a4a4;
--base-color-06: #bbbbbb;
--base-color-07: #d1d1d1;
--base-color-08: #e8e8e8;
--base-color-09: #ffffff;
コードだと、よく分かりませんが、画像で表すと次のような感じです。
ライトモードがデフォルトだと思いますので、CSS ファイルの、:root { }
に含めてしまいます。
:root {
/* -- ライトモード・カラー -- */
--font-color: var(--base-color-00);
--main-background-color: var(--base-color-09);
--header-background-color: var(--base-color-08);
--header-font-color: var(--base-color-00);
--left-menu-background: var(--base-color-07);
--left-menu-border: var(--base-color-06);
--left-menu-sub-background: var(--base-color-08);
--left-menu-top-hover: var(--base-color-06);
--left-menu-sub-hover: var(--base-color-09);
--toggle-switch-theme-on-font: var(--base-color-01);
--toggle-switch-theme-off-font: var(--base-color-06);
--toggle-switch-theme-on-color: var(--base-color-09);
--toggle-switch-theme-off-color: var(--base-color-03);
--toggle-switch-ball: var(--base-color-08);
--toggle-switch-ball-disabled: var(--base-color-05);
--toggle-switch-background-color: var(--base-color-09);
--toggle-switch-background-color-disabeled: var(--base-color-06);
--toggle-switch-icon: var(--base-color-00);
--toggle-switch-box-shadow: var(--base-color-03);
--scrollbar-track-piece: var(--base-color-07);
--scrollbar-track-thumb: var(--base-color-05);
--sheet-background-color: var(--base-color-09);
それに対して、「ダークモード」の設定も行います。
/* -- ダークモード・カラー -- */
[data-theme="dark"] {
--font-color: var(--base-color-09);
--main-background-color: var(--base-color-00);
--header-background-color: var(--base-color-01);
--header-font-color: var(--base-color-09);
--left-menu-background: var(--base-color-02);
--left-menu-border: var(--base-color-00);
--left-menu-sub-background: var(--base-color-03);
--left-menu-top-hover: var(--base-color-04);
--left-menu-sub-hover: var(--base-color-05);
--toggle-switch-theme-on-font: var(--base-color-01);
--toggle-switch-theme-off-font: var(--base-color-06);
--toggle-switch-theme-on-color: var(--base-color-09);
--toggle-switch-theme-off-color: var(--base-color-03);
--toggle-switch-ball: var(--base-color-08);
--toggle-switch-ball-disabled: var(--base-color-05);
--toggle-switch-background-color: var(--base-color-00);
--toggle-switch-background-color-disabeled: var(--base-color-06);
--toggle-switch-icon: var(--base-color-09);
--toggle-switch-box-shadow: var(--base-color-03);
--scrollbar-track-piece: var(--base-color-07);
--scrollbar-track-thumb: var(--base-color-02);
--sheet-background-color: var(--base-color-01);
}
ダークモード・カラーの、CSS ファイル内にある [data-theme="dark"] { }
は、属性セレクターの値に一致するすべての要素に適用します。
属性セレクター
https://developer.mozilla.org/ja/docs/Web/CSS/Attribute_selectors
この例の場合、「要素」の指定がありませんが、アスタリスク(*)を先頭に付けても同じ結果になります。いわば、[data-theme="dark"] = *[data-theme="dark"] ということになります。
実際の「ダークモード」の切替は、index.html
に設置されているトグルスイッチで行います。
const toggleSwitch = $('#checkbox-dark-toggle');
toggleSwitch.change((e) => {
if (e.target.checked) {
doc.attr('data-theme', 'dark'); // ダークモードに設定
}
else {
doc.attr('data-theme', 'light'); // ライトモードに設定
}
})
jQuery を使っているのなら上記のように、attr
で値を指定してください。もし、jQuery を使っていないならば、
document.getElementById('checkbox-dark-toggle').setAttribute('data-theme', 'dark');
とでも、してください。
SVG イメージのカラー操作
ダークモード切り替えのとき、単純なアイコン等の画像なら、ファイルの .svg
形式を使うと、カラー変更できて便利です。
実際、サンプルアプリケーションで使っているのは、以下の SVG 画像です。
これを HTML 内のソースに組み込むには、moon.svg
をテキストエディターなどで開きます。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g id="moon-icon" transform="matrix(1,0,0,1,16,16)">
<g>
<path d="M459.782,347.328C455.494,342.048 448.294,340.096 441.958,342.368C424.198,348.736 404.934,352 384.646,352C287.59,352 208.646,273.024 208.646,176C208.646,117.6 237.478,63.232 285.766,30.528C291.238,26.816 293.862,20.128 292.39,13.696C290.918,7.264 285.638,2.4 279.078,1.44C271.59,0.352 264.134,0 256.646,0C124.294,0 16.646,107.648 16.646,240C16.646,372.352 124.294,480 256.646,480C340.646,480 417.062,437.312 460.998,365.824C464.55,360.032 464.038,352.64 459.782,347.328Z" style="fill-rule:nonzero;"/>
</g>
</g>
</svg>
ファイル表示としては必要ですが、HTML ソース内に組み込むには不要な記述がたくさんあるので、以下のように削り、修正します。
<svg display="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="moon-icon" viewBox="0 0 512 512">
<g>
<path d="M459.782,347.328C455.494,342.048 448.294,340.096 441.958,342.368C424.198,348.736 404.934,352 384.646,352C287.59,352 208.646,273.024 208.646,176C208.646,117.6 237.478,63.232 285.766,30.528C291.238,26.816 293.862,20.128 292.39,13.696C290.918,7.264 285.638,2.4 279.078,1.44C271.59,0.352 264.134,0 256.646,0C124.294,0 16.646,107.648 16.646,240C16.646,372.352 124.294,480 256.646,480C340.646,480 417.062,437.312 460.998,365.824C464.55,360.032 464.038,352.64 459.782,347.328Z" style="fill-rule:nonzero;"/>
</g>
</symbol>
</svg>
あらかじめ、svg タグの中にある、display="none"
にして HTML ソース内に記述しておくのがミソです。HTML ファイル中に埋め込まれていますが、非表示の状態になります。
このアイコンは、実際ライトテーマ/ダークテーマの切り替えスイッチのアイコンに使われています。
そして、HTML 冒頭に記述した SVG ファイルデータは、以下のように使います。
<svg class="dark-mode-svg">
<use x="0" y="0" xlink:href="#moon-icon"></use>
</svg>
スタイルシートでは、以下のように「月」マークの部分の色を fill
で指定します。
.dark-mode-svg {
fill: #333333; /* 黒色 */
margin-top: 2px;
width: 16px;
height: 16px;
}
基本的に、この SVG 画像は、背景が透明ですので、配置された要素の background
カラーの上に表示されます。
つまり、月のマークの色のみ fill
の値を変え、そこだけ色を変えています。
macOS の「設定」から「外観」を変更されたときの処理
macOS の「設定」から、「外観」をリアルタイムで変更できます。対応アプリケーションなら、自動的にライト/ダークが切り替わると思います。
もちろん、Electron にも、その設定変更イベント発火を感知する仕組みが備わっています。
nativeTheme - Electron ドキュメント
https://www.electronjs.org/docs/api/native-theme#event-updated
const nativeTheme = require('electron');
nativeTheme.on("updated", () => {
// macOS の「設定」→「一般」から「外観」を変更されたときのイベント発火
});
ただ、この辺りの実装は、主にアプリケーション側にあると思います。「自動」「ライト」「ダーク」のいずれかをユーザーに選択させ、もし「自動」を設定しているのならば、macOS 全体の「外観」カラーに合わせて、アプリケーションの方の色も変更されるべきでしょう。
手動で「ライト」「ダーク」が選択されているならば、このイベント発火は無視しても良いでしょう。
ダークモード設定の保存・読み込み
ここででは、ダークモード切り替えの説明がメインですので、設定保存は HTML5 の「localStorage」を使います。もう少し本格的に設定の永続化を行いたい場合は適宜、「electron-store」を使うなどしてください。
読み込みは、document
が準備できた段階で良いでしょう。
$(document).ready(function(){
// ローカルストレージから「ダークモード」の設定を読み込む
const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null;
//・・・
})
保存は、「ダークモード」のトグル切替スイッチが変更される度に行います。
// ダークモード切り替えスイッチ
const toggleSwitch = $('#checkbox-dark-toggle');
toggleSwitch.change((e) => {
if (e.target.checked) {
doc.attr('data-theme', 'dark');
localStorage.setItem('theme', 'darks');
}
else {
doc.attr('data-theme', 'light');
localStorage.setItem('theme', 'light');
}
})
メニューバー(Tray)のダークモード切り替え対応
忘れがちですが、メニューバーに独自アイコンを表示している場合は、ダークモード切り替えに対応しておく必要があります(カラーアイコンで両対応できてるのなら問題無し)。
もしマルチプラットフォームで開発し、Windows 10 の場合には、macOS の「ダークモード」に合わせた Tray
アイコンを使った方が無難です。
ちなみに、Electron の Tray
を使ったアプリケーションの実装については、別の記事で書きました。よろしければご参考にしてください。
参考:Electron で Tray を使う(macOS, Windows)
https://qiita.com/hibara/items/4a3c26817e5449ebf722