なんとかは黒に染まれ、とかなんとか
すでにお気づきの方もいらっしゃるかもしれないが、このブログにダークモードを追加した。ページ上部の🌓(または☀)ボタンで切り替えられるので試してみてください。
作るものの概要
以下のようなものを作る。
- OS側がダークモードであっても、初回訪問時(デフォルト)はライトモードで表示
- 記事上でダーク・ライトを切り替えられる
- 次回訪問時は前回切り替えた設定が有効になる
初回訪問時に一律ライトモードで表示する理由は、このブログを訪問する前に訪問者が開いていたページはおそらく白背景であろう、という予測から。個人的にはダークモードのほうが好きだけど、ウェブページ全般におけるダークモード対応状況は限定的といえるため、初回は馴染みのカラーリングで表示するのが無難だろうと判断した。デフォルトのモードは外的要因等で今後変えるかもしれない。
また、モードは閲覧者が切り替えて、どちらも試せるようにしたい。なのでCSSの prefers-color-scheme
は使わなかった。これはCSSだけでOS側のライト・ダークどちらを指定されているか取得できて便利なんだけど、OSがダークモードならCSSもダークでしか表示できないという弱点がある。
実装
CSS Variables にライト・ダーク両方の色情報をセット
まずダークモードの配色を決める必要がある。Material Designのダークモード(ダークテーマ)のドキュメントはWebの世界にも転用させやすかった。ダークモードにおける基本的な考え方からアクセシビリティに配慮したカラーリング方法、実例での注意点等々をざっと掴むことができる。
配色を決めたら実装に移る。準備段階としてライト・ダークモードの色情報を一元管理できるようにしておく。以下のようにSassとCSSの変数を組み合わせた。
// ライトモードの配色 :root{ --primary: #4445ab; --background: #fff; --text-main: #444; ... } // ダークモードの配色 :root[data-theme-mode='dark'] { --primary: #8e8fff; --background: #1e1e20; --text-main: #dedee4; ... } $primary: var(--primary); $background: var(--background); $text-main: var(--text-main); ... // 各コンポーネントではsassの変数で呼び出す body { background: $background; color: $text-main; }
ダークモードの色切り替えは <html>
のdata属性で行う。このあとのステップで <html>
にダークモードのとき data-theme-mode="dark"
属性をつけるので、その場合はダーク用の色が選ばれ、そうでないときはライト用の配色となる。このようなスタイルの出し分けには body の class がよく使われるが、今回は CSS Variables で慣習的に使う :root
を活かしたかったので html 側に書くことにした。
CSS Variablesによる方法が便利なのは、デフォルトでは表面上はなんの変化も起こらないので、閲覧者に気づかれることなく一旦この状態で本番反映してしまえる点だ。最後の最後にダークモードに切り替えるUIをリリースするまで、安心して調整を続けられる。
ただし以下に書くように、場合によっては既存のCSSを広範囲に見直す必要がある。ある意味これが一番大変かもしれない(実際この工程が一番時間かかった)。
変数名
変数名を color: $black;
などの色名にしていると、ダークモードのときは色が反転してるので気まずい(以下の例参照)。 $background
や $border
といった要素にちなんだ名前にするか、$primary
のような抽象的な名前にすると取り回しやすくなる。
body { color: $black; } // ライトモード :root{ --black: #222; } // ダークモード :root[data-theme-mode='dark'] { --black: #aaa; 👈??? }
ボックスシャドウの対応
また、単純に配色を変えただけではうまくいかないケースも出てくる。筆頭がボックスシャドウ。暗い背景ではシャドウを表示することが困難になってしまう。表面を明るくする解決法が、Material Designをはじめ広く採られている。
$dark-mode: "[data-theme-mode='dark']"; // _variable.scssなんかに書いておくと便利 .archive-entry { background: $background; box-shadow: 0 4px 16px rgba(0,0,0,.16); // ダークモード時のスタイル // 管理しやすいように一旦sassで変数化したものを呼び出している #{$dark-mode} & { background: $background-secondary; // 基本の背景色より少し明るい } }
lighten, darken
sassで明度を簡単に調整できる lighten
や darken
はよく使っていたのだが、CSS Variablesを噛ませるとコンパイルに失敗した。うまい解決法がなかったので、諦めてこれらの箇所は書き直した。なんか回避策あったりするんですかね?
// これはコンパイルできる $primary: #4445ab; $primary-variant: darken($primary, 10%); // こっちはできない :root{ --primary: #4445ab; } $primary: var(--primary); $primary-variant: darken(var(--primary), 10%); // 仕方なくこう書いた(素朴) $primary: #4445ab; $primary-variant: #353687;
既存のアイコン類の対応
ダークモード時の色変えを可能にするため、全部インラインSVGにした。インラインになっていれば fill
でパスの色指定をCSSで行える。
.example-icon path { fill: $primary; }
ダークモードに切り替えるボタン
CodePenでデモが見られるこちらの方法を参考にした。
テーマモードをトグルするUIにチェックボックスを使っているので簡易に実装できる。また、モードをlocalStorageに保存することで次回訪問時も設定が保持される。
このへんはアレンジした。
- デフォルトはライトモードにした
- カラーモード切り替え時
<html>
のdata属性でスタイルを出し分けたいので、body.classList
を操作する部分をdocument.documentElement.setAttribute('data-theme-mode', 'light(またはdark)');
にした
以上のように準備作業が地味ではあるが、いざ手元で切り替えテストをやってみると、ぱっと全体の印象が変わってまあまあ達成感がある。