🦉

ダークモード入門

8 min read

はじめに

WEBサイトにダークモードを実装する際に調べたことの覚書です。
以下のような内容です。

  • OSの設定によってCSSのメディアクエリでスタイルを適用する
  • 切り替えスイッチによるモードの変更
  • おまけ:Tailwind CSSでダークモード

今回作成したコードの全体は以下になります。

https://github.com/K-shigehito/darkmode-preview

CSSのメディアクエリでスタイル適用する

最初に、OSで設定されたモードによって、CSSのメディアクエリprefers-color-schemeで適用するスタイルを切り替える方法です。:rootに変数で色を指定して利用しています。

https://developer.mozilla.org/ja/docs/Web/CSS/@media/prefers-color-scheme
sample-01/style.css
:root {
  --cBackground: #fedfe1;
  --cText: #656765;
}

/* OSの設定がダークモード時のスタイル */
@media (prefers-color-scheme: dark) {
  :root {
    --cBackground: #64363c;
    --cText: #fcfaf2;
  }
}

.test {
  width: 100px;
  height: 100px;
  margin: 0 auto;
  background-color: var(--cBackground);
}
.test__text {
  color: var(--cText);
  text-align: center;
  line-height: 100px;
}

prefers-color-schemeのブラウザの対応状況です。
https://caniuse.com/?search=prefers-color-scheme
こちらの記事でPolyfillも紹介されていました。(今回は使用していません)
https://coliss.com/articles/build-websites/operation/css/dark-mode-website-with-css.html

この状態でOSのモードを切り替えるとスタイルが切り替わります。

OSのモード切替

OSのモード切替方法は、windows10の場合 [設定 > 個人設定] から規定のアプリモードを選択しますから切り変えられます。

今回macOSでは検証していませんが、以下で設定できるようです。
https://support.apple.com/ja-jp/guide/mac-help/mchl52e1c2d2/mac

また、ブラウザの開発ツールでエミュレートすることもできます。

  • Chrome
    メニューから[More tools > Rendering]で「Emulate CSS media feature prefers-color-scheme」でモードを選択します。

  • Firefox
    「インスペクター」の「ルール」内のアイコンでモードの切り替えができます(旧バージョンでは設定方法とアイコンが変わっているかもしれません。今回は"88.0.1"を使用しています)。

切り替えスイッチによるモードの変更

次に切り替えスイッチを設置して、UI上でモードの切替を行えるようにします。
まず、OSのモード設定をJavaScriptのmatchMediaを利用してメディアクエリprefers-color-schemeの値で判定して、ルート要素<html>にクラスの追加・削除をします。イベントリスナーから変更を検知しています。

sample-02/main.js
// OSの設定がダークモード
const osDark = window.matchMedia("(prefers-color-scheme: dark)");

// ダークモードがオンの時に実行する処理
function darkModeOn() {
  document.documentElement.classList.add("darkmode"); // ルート要素<html>にクラスを追加
}
// ダークモードがオフの時に実行する処理
function darkModeOff() {
  document.documentElement.classList.remove("darkmode"); // クラスの削除
}

// イベントリスナー
const listener = (event) => {
  if (event.matches) {
    darkModeOn();
  } else {
    darkModeOff();
  }
};

// リスナー登録
osDark.addEventListener("change", listener);

// 初期化処理
listener(osDark);

matchMediaの記述についてはこちらの記事を参考にさせて頂きました。(ありがとうございます!)

https://zenn.dev/yuki0410/articles/878f4afbff6668d4e28a-2

ダークモード用のクラス.darkmodeを記述します。

sample-02/style.css
:root {
  --cBackground: #fedfe1;
  --cText: #656765;
}

/* OSの設定がダークモード時のスタイル */
:root.darkmode {
  --cBackground: #64363c;
  --cText: #fcfaf2;
}
...

これで先程の「CSSのメディアクエリでスタイル適用する」と同様にOSの設定によってスタイルが切り替えられるようになりました。

切り替えボタンの設置

次に切り替えボタンを設置します。今回は以下のツールを使用して簡単に作成しています。

https://proto.io/freebies/onoff/
作成されたhtmlを貼り付けて使用します。
sample-02/index.html
...
<!-- 切り替えスイッチ -->
<div class="onoffswitch">
  <input
    type="checkbox"
    name="onoffswitch"
    class="onoffswitch-checkbox"
    id="myonoffswitch"
    tabindex="0"
  />
  <label class="onoffswitch-label" for="myonoffswitch">
    <span class="onoffswitch-inner"></span>
    <span class="onoffswitch-switch"></span>
  </label>
</div>
...

スイッチにふられたIDから要素を取得して、チェックボックスのオンオフに応じてスタイルを切り替えます。

sample-02/main.js
// ...

// スイッチのinput要素(checkbox)
const modeSwitch = document.getElementById("myonoffswitch");

// ...

// スイッチの操作に応じて切り替え処理
modeSwitch.addEventListener("change", () => {
  if (modeSwitch.checked) {
    darkModeOn();
    } else {
    darkModeOff();
  }
});

これでスイッチでモードの切替ができるようになりました。

スイッチとモードを一致させる

現状OSの設定がダークモードになっていた場合に、初回ロードでページ表示はダークモード、スイッチはライトモードとちぐはぐな状態となってしまいます。

そこで、以下のようにモード切り替え関数に追記してスイッチとモードを一致させます。

sample-02/main.js
 // ...
 function darkModeOn() {
   document.documentElement.classList.add("darkmode");
+  modeSwitch.checked = true;
 }
 function darkModeOff() {
   document.documentElement.classList.remove("darkmode");
+  modeSwitch.checked = false;
 }

これでOSの設定がダークモードの場合はスイッチもDarkになるようになりました。

スイッチの状態をブラウザーで保持する

最後に、スイッチの状態を保持できるようにしてみます。
現状スイッチでモードを切り替えた後にページをリロードするとOSの設定に戻ってしまいます。ブラウザのストレージを利用してこれに対応します。
ブラウザのストレージはlocalStoragesessionStorageがありますが、今回はセッションが持続する間だけ状態を保持するようにsessionStorageを使用しました。

https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API
sample-02/main.js
 // ...

 // スイッチの操作に応じて切り替え処理
 modeSwitch.addEventListener("change", () => {
   if (modeSwitch.checked) {
     darkModeOn();
+    sessionStorage.setItem("darkMode", "on");
   } else {
     darkModeOff();
+    sessionStorage.setItem("darkMode", "off");
   }
 });

+// ロード時の状況に応じて切り替え
+if (sessionStorage.getItem("darkMode") === "on") {
+  darkModeOn();
+} else if (sessionStorage.getItem("darkMode") === "off") {
+  darkModeOff();
+}

sessionStorage.setItem("key", "value")の形でkeydarkModevaluにはスイッチの操作によってon/offをセットしています。
そして、ページロード時にsessionStorage.getItem("darkMode")darkModeの値を確認してon/offそれぞれの処理を行うようにします。
これでページをリロードしても、スイッチとモードの状態が保持されるようになりました。

Tailwind CSSでダークモードを使う

最後におまけとしてTailwind CSSでダークモードの設定をしてみます。
Tailwind CSSの導入については公式に詳しく書かれていますので省略します。(私も以前に導入について少し書いてみたので、参考にしてもらえたら嬉しいです)

https://tailwindcss.com/docs/installation
https://zenn.dev/chabatake_i/articles/tailwindcss_introduction

OSの設定によってモードの切り替え

OSの設定によってモードを切り替える場合、Tailwind CSSの"v2"からは設定ファイル(tailwind.config.js)にdarkModeの設定をmediaとするだけで利用できます。

sample-03/tailwind.config.js
 module.exports = {
   purge: [],
-  darkMode: false, // or 'media' or 'class'
+  darkMode: 'media',

 // ...
 };

これで擬似クラス(variants)に"dark"が追加さるので、dark:でダークモード時のクラス定義ができるようになります。
以下のように記述すると「sample-01」と同じようにスタイルがあたります。

sample-03/dist/index.html
...
<div class="mt-[6px] mx-auto w-[100px] h-[100px] bg-[#fedfe1] dark:bg-[#64363c]">
  <div class="text-center leading-[100px] text-[#656765] dark:text-[#fcfaf2]">
    test-03
  </div>
</div>
...

サンプルはv2.1から利用できるJust-in-Time Mode(JITモード)で記述していますが、darkMode自体はJITモードと関係なく利用できます。
https://tailwindcss.com/docs/just-in-time-mode#enabling-jit-mode

手動でのモードの切替

手動でモードの切替を行う場合は、tailwind.config.jsdarkModeclassにします。

sample-03/tailwind.config.js
 module.exports = {
   purge: [],
-  darkMode: 'media',
+  darkMode: 'class',
 // ...
 };

これでdarkクラスが先祖要素に付与されている場合にダークモードが適用されます。

以下の公式ドキュメントを参考に、ボタンを設置して切り替えができるようにしてみました。

https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually
sample-03/dist/index.html
...
<!-- 切り替えボタン -->
<button
  id="toggleButton"
  class="block w-[100px] h-[40px] mx-auto rounded-md hover:opacity-90 bg-[#64363c] text-[#fcfaf2] dark:bg-[#fedfe1] dark:text-[#656765]"
>
  change
</button>
...
sample-03/src/main.js
// 初期化(OSの設定によってモードとsessionStorageを設定)
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  document.documentElement.classList.add('dark')
  sessionStorage.theme = 'dark';
} else {
  document.documentElement.classList.remove('dark')
  sessionStorage.theme = 'light';
}

// ボタン要素のクリックイベント
const toggleButton = document.getElementById('toggleButton');
toggleButton.addEventListener("click", () => {
  toggleDarkMode();
})

// モードとsessionStorageの切り替え関数
const toggleDarkMode = () => {
  if (sessionStorage.theme === 'dark') {
    document.documentElement.classList.remove('dark')
    sessionStorage.theme = 'light';
  } else {
    document.documentElement.classList.add('dark')
    sessionStorage.theme = 'dark';
  }
}

ボタンで切り替えができるようになりました。

おわりに

以上が今回ダークモードについて調べた内容です。

以前GitHubのUI上でネコのイラストの切り替えスイッチがあったと思うのですが、それが好きだったのでなくなってしまった時とても残念でした(さよなら~みたいなアニメーション付きで去っていった気がします...)。
その時に自分でもダークモードを実装してみたいなと思うようになり、今回記事にしてみました。参考になれば幸いです!

また、内容に誤り等ありましたらご指摘いただけるとありがたいです。