1
2

More than 3 years have passed since last update.

Electron で macOS のダークテーマ切替機能を実装する

Last updated at Posted at 2020-10-08

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 とコマンドを入力し、インストールしてからビルドしてください。

ライトモード ダークモード
screen-shot-light.png screen-shot-dark.png

参考にしたのは、このサイトです。
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);)を使ってアクセスします。

今回のサンプルアプリケーションでは、スタイルシート冒頭にマテリアルデザインを意識した、ベースとなるグラデーションを定義しました。

style.css
: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;

コードだと、よく分かりませんが、画像で表すと次のような感じです。
マテリアルカラー.png
ライトモードがデフォルトだと思いますので、CSS ファイルの、:root { } に含めてしまいます。

style.css
: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);

それに対して、「ダークモード」の設定も行います。

style.css
/* -- ダークモード・カラー -- */
[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 に設置されているトグルスイッチで行います。

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 画像です。
moon.png
これを HTML 内のソースに組み込むには、moon.svg をテキストエディターなどで開きます。

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 ソース内に組み込むには不要な記述がたくさんあるので、以下のように削り、修正します。

moon.svg
<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 ファイルデータは、以下のように使います。

index.html
<svg class="dark-mode-svg">
  <use x="0" y="0" xlink:href="#moon-icon"></use>
</svg>

スタイルシートでは、以下のように「月」マークの部分の色を fill で指定します。

style.css
.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

main.js
const nativeTheme = require('electron');
nativeTheme.on("updated", () => {
  // macOS の「設定」→「一般」から「外観」を変更されたときのイベント発火
});

ただ、この辺りの実装は、主にアプリケーション側にあると思います。「自動」「ライト」「ダーク」のいずれかをユーザーに選択させ、もし「自動」を設定しているのならば、macOS 全体の「外観」カラーに合わせて、アプリケーションの方の色も変更されるべきでしょう。

手動で「ライト」「ダーク」が選択されているならば、このイベント発火は無視しても良いでしょう。

ダークモード設定の保存・読み込み

ここででは、ダークモード切り替えの説明がメインですので、設定保存は HTML5 の「localStorage」を使います。もう少し本格的に設定の永続化を行いたい場合は適宜、「electron-store」を使うなどしてください。

読み込みは、document が準備できた段階で良いでしょう。

index.html
$(document).ready(function(){
  // ローカルストレージから「ダークモード」の設定を読み込む
  const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null;
  //・・・
})

保存は、「ダークモード」のトグル切替スイッチが変更される度に行います。

index.html
// ダークモード切り替えスイッチ
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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2