【JavaScript】ダークモードの切り替えボタン

ダークモード始めましたか？

エディターをダークモードにしたのがきっかけで、OS、アプリとダークモード始めました！
ダークモードに慣れてしまうと、ダークモード非対応のサイトがめっちゃ眩しい…:persevere:

というわけで、ユーザーの好みに応じてサイトのカラーモードを切り替えられるボタンを作ってみました！

ちなみに…
私はドラキュラテーマの大ファンです。
エディターだけかと思ったら、たくさんのアプリにも対応してくれています。
いっそのことWindowsのOS自体も対応してほしい！

以下のアプリにドラキュラテーマを適用しています。

* Visual Studio Code
* Chrome
* Firefox
* Thunderbird
* Slack

FileZilla にも対応してくれないかな…

https://draculatheme.com/

## 仕様

ブラウザを開いてる間変更したカラーモードを保存するため、セッションストレージを利用します。

* ページ読み込み時のカラーモード
  * セッションストレージなし
    * OSのカラーモード
  * セッションストレージあり
    * OSのカラーモードから反転
* 切り替えボタンをクリック
  * OSのカラーモードと異なる
    * `html` 要素に反転用の `class` を付与
    * セッションストレージに反転用の値を保存
  * OSのカラーモードと同じ
    * `html` 要素に反転用の `class` が付与されていたら削除
    * セッションストレージに反転用の値が保存されていたら削除
* OSのカラーモードを変更
  * `html` 要素に反転用の `class` が付与されていたら削除
  * セッションストレージに反転用の値が保存されていたら削除

## HTML

ページ読み込み時、セッションストレージの判定を行う必要があります。
これはページが描画される前に行わないと、OSのカラーモードが一瞬チラついてしまいます。
そのため、`head` 要素のなるべく上のほうに記述し、実行させます。

```html:HTML
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, viewport-fit=cover">
    <meta name="color-scheme" content="light dark">
    <script>
      // セッションストレージの判定を行う
      if (sessionStorage.getItem('colorMode') === 'invert') {
        const colorMode = sessionStorage.getItem("colorMode");
        document.documentElement.classList.add(colorMode)
      }
    </script>
  </head>
  <body>
    ...
  </body>
</html>
```

## Sass

`@media` の `prefers-color-scheme` メディア特性でライトとダークの時の設定をします。
`html` 要素に反転用の `class` が付与された時の設定もします。
また、切り替えボタンのアイコンの変更も、反転用の `class` を継承して設定します。

```sass:Sass
@mixin light-colors {
  --color: #333;
  --bg-color: #fff;
}
@mixin dark-colors {
  --color: #f2f2f2;
  --bg-color: #282a36;
}
@media (prefers-color-scheme: light) {
  :root {
    @include light-colors;
    .btn {
      &::before {
        content: "\f185";
      }
    }
    &.invert {
      @include dark-colors;
      .btn {
        &::before {
          content: "\f186";
        }
      }
    }
  }
}
@media (prefers-color-scheme: dark) {
  :root {
    @include dark-colors;
    .btn {
      &::before {
        content: "\f186";
      }
    }
    &.invert {
      @include light-colors;
      .btn {
        &::before {
          content: "\f185";
        }
      }
    }
  }
}
body {
  background-color: var(--bg-color);
}
.btn {
  color: var(--color);
  &::before {
    font-family: "Font Awesome 6 Free";
  }
}

```

## JavaScript

```javascript:JS
class ColorModeToggleButton {
  #colorModeMql
  #colorModeBtn
  #strageKey
  #strageVal
  constructor() {
    this.#colorModeMql = window.matchMedia('(prefers-color-scheme: dark)')
    this.#colorModeBtn = document.getElementById('js-colorModeBtn')
    this.#strageKey = 'colorMode'
    this.#strageVal = 'invert'
    this.#init()
  }

  #init() {
    this.#changeOsColorMode()
    this.#clickColorModeBtn()
  }

  // OSのカラーモードから反転
  #invertColorMode() {
    sessionStorage.setItem(this.#strageKey, this.#strageVal)
    document.documentElement.classList.add(this.#strageVal)
  }

  // OSのカラーモードに戻す
  #resetColorMode() {
    sessionStorage.removeItem(this.#strageKey)
    document.documentElement.classList.remove(this.#strageVal)
  }

  // OSのカラーモードの切り替え
  #changeOsColorMode() {
    this.#colorModeMql.addEventListener('change', () => {
      // セッションストレージが設定されている場合は削除（OSの設定優先）
      if (sessionStorage.getItem(this.#strageKey)) {
        this.#resetColorMode()
      }
    })
  }

  // カラーモードボタンをクリック
  #clickColorModeBtn() {
    this.#colorModeBtn.addEventListener('click', () => {
      sessionStorage.getItem(this.#strageKey)
        ? this.#resetColorMode()
        : this.#invertColorMode()
    })
  }
}
const colorModeToggleButton = new ColorModeToggleButton();
```

## DEMO

<p class="codepen" data-height="500" data-default-tab="result" data-slug-hash="vYjGMjG" data-user="taqumo" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/taqumo/pen/vYjGMjG">
  Dark Mode Toggle Button</a> by Takuya Mori (<a href="https://codepen.io/taqumo">@taqumo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>