サイトにダークモード切り替えボタンを置こう

こんな感じのをつくりました（絵文字をクリックしてね！）。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="result" data-user="IKEMAKI" data-slug-hash="vYERLrG" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="dark mode support js">
  <span>See the Pen <a href="https://codepen.io/IKEMAKI/pen/vYERLrG">
  dark mode support js</a> by MAKI I (<a href="https://codepen.io/IKEMAKI">@IKEMAKI</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# はじめに

ここのところダークモードがブイブイいわせてますね。アプリや記事メインのWebサイトはどんどこ対応していってる印象です。
LINEのダークモード表示がかっこよくて好きです。  

自分はまだお仕事のWeb制作でダークモード対応をしたことはないのですが、これからありそうだよね～と話が出たり、個人的に眩しいのが苦手でダークモード好きだったりなどの理由でいじってみました。ﾋｱｳｨｰｺﾞ

# ざっくり何してるか

- 見てる人のパソコンやスマホがダークモードかライトモードか調べて、設定されてたらあわせる
- 設定されてなかったら、デフォルトのダークモードで表示する（私がダークモード好きだから）
- チェックボックスを押されたらモードを切り替えて、ローカルストレージにどっちを選んだか保存しておく（次回訪問時には選んだモードがデフォルトになる）
- 太陽と月の絵文字を置いといて、切り替えるたびぐるぐる回す

# コード

## HTML

```HTML
<!-- 表示を切り替えてみたい要素  -->
<p class="msg light">light mode</p>
<p class="msg dark">dark mode</p>

<!-- モード切替スイッチ -->
<div class="switch">
  <label class="switch-label">
    <input type="checkbox" id="js_mode_toggle">
    <span class="switch-mode" id="js_rotate"></span>
  </label>
</div>
```
ぶっちゃけHTMLはなんでもいいです。
今回は絵文字もCSSで表示しちゃってますし、モードを手動で切り替える機能がいらなければユーザー側のスイッチも不要なので、好きな感じにしちゃっておｋです。  

## CSS

```CSS
/* デフォルトはダークモード表示 */
body {
  margin: 0;
  padding: 1em 2em;
  color: #AAAE8D;
  background-color: #1D1D2C;
  transition-duration: 1s;
  transition-property: color, background-color;
}
.msg {
  font-size: 30px;
  font-weight: bold;
}
.dark {
  display: block;
}
.light {
  display: none;
}

/* ライトモードのとき */
.js-mode-light {
  color: #000;
  background-color: whitesmoke;
}
.js-mode-light .light {
  display: block;
}
.js-mode-light .dark {
  display: none;
}

/* モード切り替えボタン */
input[type="checkbox"] {
  display: none;
}
.switch {
  position: fixed;
  top: .5em;
  right: .5em;
  font-size: 60px;
}
.switch-label {
  cursor: pointer;
}
.switch-mode {
  position: absolute;
  top: -120px;
  right: -120px;
  display: block;
  width: 200px;
  height: 200px;
  transform: rotate(0);
  transition: transform 1s;
}
/* 月と太陽を対角線上に設置しておく。
   設定したspanを回したら太陽がひっくり返ってしまったので、transform scaleで上下反転した */
.switch-mode::before {
  content: "🌙";
  position: absolute;
  left: 0;
  bottom: 0;
}
.switch-mode::after {
  content: "🌄";
  position: absolute;
  right: 0;
  top: 0;
  transform: scale(1,-1);
}
```

今回はモードを判定するCSSのメディアクエリは使っていません。またあとで説明します。

表示の切り替えは、ライトモードを設定されたらjsでbodyに`js-mode-light`というクラスをつけるようにしました。ライトモード時にあてたいスタイルを`.js-mode-light`に書いていけばおｋです。
たとえばレスポンシブ対応で、スマホ表示のときは`pc`クラスを非表示にする……みたいなのと同じ感覚です。  

ただ、モードを手動で切り替えられるスイッチを付けた場合、レスポンシブ対応のようにただ表示／非表示を切り替えてしまっては味気ないので、色などは適宜`transition`をうまく使って設定したほうが良いです。  

また変化が広範囲におよんで事故る可能性も高いので、`transition-property`でちゃんと適用したいプロパティを定義しておくのも大事です。日頃からとりあえず流れるように`transition: all .2s;`と打ってしまう私のようではだめということです。  

色とか数値は全部テキトーです。

## JS

``` JS
// 要素やクラスを指定しておく
const checkToggle = document.getElementById('js_mode_toggle');
const rotateIcon = document.getElementById('js_rotate');
const classLight = 'js-mode-light';

// デバイスがライトモードかどうかチェック
const isLight = window.matchMedia('(prefers-color-scheme: light)').matches;

// ローカルストレージに保存するための適当なKey名
const keyLocalStorage = 'whike-theme-mode';

// ローカルストレージの情報を取得
const localTheme = localStorage.getItem(keyLocalStorage);

// 絵文字を回転させる角度
let nowRotate = 0;

// ローカルストレージの中身と、端末がライトモードかどうか（ie,edgeには無意味）をチェック
if(localTheme === 'light') {
  // ローカルストレージの情報が優先
  rotateInfinite();
  changeMode('light');
} else if(localTheme === 'dark') {
  changeMode('dark');
} else if(isLight) {
  rotateInfinite();
  changeMode('light');
}

// チェックボックスでの切り替え、選択をローカルストレージに保存
// モード切替スイッチが変更されたら発動
checkToggle.addEventListener('change', function(e) {
  // 絵文字大回転
  rotateInfinite();

  // チェックされたらライトモード、されなければダークモードにし、ローカルストレージにどちらを選んだか保存する
  if(e.target.checked) {
    changeMode('light');
    localStorage.setItem(keyLocalStorage,'light');
  } else {
    changeMode('dark');
    localStorage.setItem(keyLocalStorage,'dark');
  }
});

/**
 * テーマ切り替え
 * @param {String} mode 'light' もしくは 'dark'
 */
function changeMode(mode) {
  // 引数にしたがってbodyにクラスをつける
  // チェックボックス経由で変更かかったときはいいんだけど、ローカルストレージとかからモードを変えた場合にチェック状態がおかしくなるので、合わせておく
  if(mode === 'light') {
    document.body.classList.add(classLight);
    checkToggle.checked = true;
  } else if(mode === 'dark') {
    document.body.classList.remove(classLight);
    checkToggle.checked = false;
  }
}

/**
 * 月と太陽アイコン無限回転
 * 呼ばれるたびに180度角度が追加されていく
 */
function rotateInfinite() {
  nowRotate += 180;
  rotateIcon.style.transform = 'rotate(' + nowRotate + 'deg)';
}
```

### デバイスのテーマを調べる

デバイスのテーマを調べるには、CSSで用意されている`prefers-color-scheme`を使います。

> prefers-color-scheme は CSS のメディア特性で、ユーザーがシステムに要求したカラーテーマが明色か暗色かを検出するために使用します。
by [prefers-color-scheme - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/@media/prefers-color-scheme)

なのでこれを使えばCSSだけで切り替えることができます。
こんなかんじで👇

```css
.dark { display: block; }
.light { display: none; }

@media (prefers-color-scheme: light) {
  .dark { display: none; }
  .light { display: block; }
}
```
ただIEとEdgeが非対応なので、そこも対応する場合は（するよね??）結局JSを使うことになるため、両方で管理したくないっという思いから今回はJSだけ使っています。

参考: [Can I use prefers-color-scheme ?](https://caniuse.com/#search=prefers-color-scheme)

で、それをJSで使うときは`window.matchMedia`を使います。
ためしに開発者ツールのコンソールに`window.matchMedia('(prefers-color-scheme: light)').matches`をコピペしてEnterすると、ライトモードならtrue、ダークモードならfalseが返ります。私はPCをダークモードにしているのでfalseになりました。
これでユーザーのデバイステーマを判定できました。

### ユーザーの選択したテーマを保存

テーマを選択するボタンを設置した以上は、次回サイト訪問時も選んだテーマになってて欲しいので、ユーザーがどちらを選んだかブラウザのローカルストレージに保存しておきます。  

保存は簡単で、`localStorage.setItem(任意のKey名, 値);`とすればおｋです。
今回は、ライトモードのときには`localStorage.setItem('whike-theme-mode','light');`、ダークモードのときには`localStorage.setItem('whike-theme-mode','dark');`としています。`whike`は私の名前です。そうそう被らないとは思いますが、固有のKeyにするため付けてます。
setItemで情報を保存したら、getItemで`localStorage.getItem(Key名);`こういう感じに取り出せます。もし設定されてなければ`null`が返ります。  
Chromeであれば、開発者ツールApplicationタブのStorage欄に、Local Storageというのがあるのでそこを展開してみると、設定したKeyとValueが確認できます。他のブラウザでもだいたい似たようなところにあります。IEでも大丈夫😉

ちなみにローカルストレージは気軽に扱えるぶん、個人情報とかログイン情報とか、大事なものは置いちゃダメですよ✊

# おわりに

最初なんとなくで月と太陽が入れ替わったら楽しいデショと思って作ったのですが、回すのが意外と面倒😂この動きにしなかったらもう少しシンプルなコードにできた気がします。
もっと上手に回せそうな気がするんですが……😵  

やってることは結構単純だし、このやり方であればIEやEdgeも動いたので、お仕事で使う分にも特に問題はなさそうかなーという印象でした。
たぶん大変なのはデザイナーさんの方ですね(¯―¯٥)  

IEとEdgeはアレですが、cssのメディアクエリでやるやり方はすぐに試せると思うのでやってみてください😍
Win10であれば`設定>個人用設定>色>色を選択する>白or黒切り替え`でPCのテーマを変えられるので（Macも似たような感じでダーク/ライト切り替え）、それに合わせてWebページが変化するのがオモロイです～やっぱ見た目が変わるのは楽しい😍😍

コードに関しては、やりたいことを中心にモリモリ作ってしまったので、もっとスマートにやれそうだという箇所があればぜひ教えてください！  

ではでは～

# 参考
- [prefers-color-scheme - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/@media/prefers-color-scheme)
- [プログラムによるメディアクエリのテスト - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/Media_Queries/Testing_media_queries)
- [Web Storage API を使用する - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API)