Windows 10では「アプリ モード(App mode)」、Android 10では「ダークモード(Dark Theme)」、macOS 10.15やiOS 13では「外観モード(Appearance)」と、それぞれ呼ばれている色モードを尊重しつつ、ユーザーの好みで明るくも暗くもできるようにした。どう実装すると効率的かを考えたかっただけなので、次期マイナーバージョンでは消える。
ウェブページでの色モードの設定には、3つの状態の管理が必要らしい。ダーク・モードにしているが特定のウェブページは白背景で見たい、またはその逆があるため、自動(OSやブラウザーでユーザーが設定したモードに従う)と、強制的にダーク、そして強制的にライトにできるべきという主張だ。実装はあまり見ないが、主張は散見される。
僕はアプリケーション側、つまりブラウザーが本来持っているべき設定だと思うので、あまり納得はしていないが、ないものはないので実装するしかなさそうだ。代替スタイルシートの切り替えなどと同じく、ブラウザーに実装されないかもしれないので、なおさらだろう。
:root {
--color-primary: var(--palette-accent-dark);
--color-background: var(--palette-lightest);
--color-surface: var(--palette-light);
--color-on-background: var(--palette-darkest);
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: var(--palette-accent-light);
--color-background: var(--palette-darkest);
--color-surface: var(--palette-dark);
--color-on-background: var(--palette-lightest);
}
}
:root.js-color-mode-light {
--color-primary: var(--palette-accent-dark);
--color-background: var(--palette-lightest);
--color-surface: var(--palette-light);
--color-on-background: var(--palette-darkest);
}
:root.js-color-mode-dark {
--color-primary: var(--palette-accent-light);
--color-background: var(--palette-darkest);
--color-surface: var(--palette-dark);
--color-on-background: var(--palette-lightest);
}
色の反転はカスタム・プロパティーで、ユーザーによる強制はクラス名で、それぞれCSSで管理する。いろいろなウェブサイトで解説されているように、パレットを定義しておいて参照するだけにしておくと簡単だ。最初の2つのルールセットが自動の時に使われるもので、あとの2つがユーザーが設定した時に使われる。詳細度がうまく機能するので、これらルールセットはほぼ順不同だが、この順序が書きやすいだろう。
const changeColorMode = (mode) => {
localStorage.setItem("color-mode", mode);
const rootClass = document.documentElement.classList;
if (mode === "dark") {
rootClass.add("js-color-mode-dark");
rootClass.remove("js-color-mode-light");
return;
}
if (mode === "light") {
rootClass.add("js-color-mode-light");
rootClass.remove("js-color-mode-dark");
return;
}
rootClass.remove("js-color-mode-dark", "js-color-mode-light");
localStorage.removeItem("color-mode");
};
ユーザー設定の保存はローカル・ストレージにした。「自動」に戻すと削除する。復元する時はhtml
要素にクラス名を振っている。そのまま保存できるカスタム・データ属性の方が書きやすいが、スタイル目的なのでクラス名を振るのが正しいと思う。ややこしいことは、ややこしいことができる仕組みの中で、できるだけやるべきだ。
また、このコードはページ読み込み中に1回呼ばれ、状態が復元される。これを非同期で実行すると、画面がフラッシュするかもしれないので、同期で実行すべきだろう。スクリプト・ファイルを分割するとよいだろう。
const onchange = (event) => {
changeColorMode(event.srcElement.value);
};
const elements = document.querySelectorAll(".js-color-mode");
for (const element of elements) {
element.addEventListener("change", onchange);
}
あとはラジオ・ボタンの切り替えで色モードを変えるコードを呼ぶだけだ。value
属性をよしなにしておけば数行で片付く。
クラス名については:has()
があれば必要なくなり、ローカル・ストレージへの保存と復元だけで済みそうなので、早く欲しい。たぶん:root:has(#color-mode-light:checked)
と書けるようになる(#color-mode-light
は、ペアになるlabel
要素用にあるであろうid
属性の値)。