OSでもWebサイト側でも切り替えられるダークモードを実装する

OSでもWebサイト側でも切り替えられるダークモードを実装する

以下の要件をもとにダークモードを実装します。

  • OSのダークモードと連動する。
  • ページ遷移してもダークモードの設定が保存される。
  • サイト側でもモードの切り替えができる。
    • (理由1)ダークモードに対応していないデバイスが存在する。
    • (理由2)OSはダークモードでもサイト側で切り替えたい場合がある
  • CSS変数で管理する

要素を設置する

サイト側でモードを切り替えるチェックボックスを設置します。

<button class="c-button-switch" aria-label="カラーテーマ切り替え">
  <input class="c-button-switch__input" id="js-switch-button" type="checkbox">
  <label class="c-button-switch__label" for="js-switch-button"></label>
</button>

CSS変数でカラーの指定

通常のダークモード対応ではメディアクエリでprefers-color-scheme: darkを指定しますが、今回はJavascriptでdata属性を使い動的に操作するので、CSSでは以下のように記述します。

/* ライトモード時 */
:root {
  --color-base: #f1f5f9;
  --color-primary: #000000;
  --color-secondary: #eee;
}

/* ダークモード時 */
[data-mode="dark"] {
  --color-base: #21242d;
  --color-primary: #fff;
  --color-secondary: #4B505E;
}

body {
  background-color: var(--color-base);
  color: var(--color-primary);
}

また、画像も背景がダークモードだと画像が粗く見えてしまうので、filterなどで彩度を下げたほうがいい場合もあります。

[data-mode='dark'] img {
    filter: grayscale(20%);
}

JavaScriptで判定、切り替えを行う

OSがダークモードかどうか判定し、ダークモード時にhtmlタグへdata-mode="dark"を動的に追加します。

チェックボックスで切り替えた際も同様にdata-mode="dark"を追加し、その情報をsessionStorageへ保存し、タブを閉じるまで設定を保持させます。
localStorageにすれば永続的に保存することもできます。

以下MDNのWindow.localStorageから引用

localStorage に保存されたデータには保持期間の制限はなく、sessionStorage に保存されたデータはセッションが終わると同時に(ブラウザが閉じられたときに)クリアされてしまう

(function () {
  const switchButton = document.getElementById('js-switch-button');
  const isDark = window.matchMedia('(prefers-color-scheme: dark)');
  const htmlElement = document.documentElement;
  const keyLocalStorage = 'theme';
  const localTheme = sessionStorage.getItem(keyLocalStorage);

  if (localTheme) { // サイト側の設定が優先
    changeMode(localTheme);
  } else if (isDark.matches) { // OSがダークモードだったら
    changeMode('dark');
  }

  switchButton.addEventListener('change', () => { // スイッチが押されたとき
    if (switchButton.checked) {
      changeMode('dark', 'set');
    } else {
      changeMode('light', 'set');
    }
  });

  function changeMode(mode, storage) {
    if (mode === 'dark') {
      htmlElement.dataset.mode = mode;
      switchButton.checked = true;

    } else if (mode === 'light') {
      delete htmlElement.dataset.mode;
      switchButton.checked = false;
    }

    if (storage === 'set') {
      sessionStorage.setItem(keyLocalStorage, mode);

    } else if (storage === 'remove') {
      sessionStorage.removeItem(keyLocalStorage);
    }
  }
}());

参考文献: ダークモード時のデザインについて

ダークモード時のデザインに関しては以下の記事が勉強になりました。