Web開発
Web標準によるダークモード
Dark Mode with Web Standards (olliewilliams.xyz)
要約
この記事は、Web標準技術を用いてウェブサイトにダークモードを実装する方法について解説しています。ユーザーのOS設定を尊重する`prefers-color-scheme`メディアクエリと、サイト固有の設定を可能にする`color-scheme`メタタグやJavaScriptの使用法を説明しています。また、画像やグラデーションでの`light-dark()`関数の活用や、将来的な高度なスタイル変更の可能性についても触れています。
全文翻訳
Web標準によるダークモード
ユーザーのOS設定を尊重することは、CSSの`prefers-color-scheme`メディアクエリを使用すれば簡単です。しかし、それだけでは不十分かもしれません。ユーザーはサイトごとに好みをカスタマイズできるべきです。例えば、アプリケーションのUIではダークモードを、コンテンツ中心のサイトでは長文を読むためにライトモードを望むかもしれません。
私たちは以下のことを行う必要があります。
ユーザーが初めてウェブサイトにアクセスした際のデフォルトとして、ユーザーのシステム設定をサポートする。
アプリケーション内のトグルで、ユーザーがシステム設定を上書きできるようにする。
ウェブページのカラースキームは、ドキュメントの`<head>`内のHTMLメタタグ、または`html`要素の`color-scheme`プロパティを通じて設定できます。低速な接続ではCSSの読み込みに時間がかかることがあるため、メタタグを使用することが推奨されるアプローチです。
ユーザーが初めてウェブサイトにアクセスした際に、`<meta name="color-scheme" content="light dark">`を設定してシステム設定を尊重してください。
ウェブアプリやサイト内のコントロールを通じてOS設定を上書きするには、JavaScriptを使用して`content`属性の値を`light`(ライトモードを強制)、`dark`(ダークモードを強制)、または`light dark`(OS設定に戻す)に更新します。
```javascript
const metaTag = document.querySelector('[name="color-scheme"]');
const savedScheme = localStorage.getItem("colorScheme");
if (savedScheme) {
metaTag.setAttribute('content', savedScheme);
}
btnlight.addEventListener('click', function() {
metaTag.setAttribute('content', 'light');
localStorage.setItem("colorScheme", "light");
});
btndark.addEventListener('click', function() {
metaTag.setAttribute('content', 'dark');
localStorage.setItem("colorScheme", "dark");
});
btnsystem.addEventListener('click', function() {
metaTag.setAttribute('content', 'light dark');
localStorage.removeItem("colorScheme");
});
```
`color-scheme`は何に影響しますか?
`light-dark()` CSS関数を介して設定された色、グラデーション、または画像
CanvasやCanvasTextのようなシステムカラー
スクロールバーの色
ボタンなどのHTML要素のデフォルトの色
(iframeドキュメントがメタタグを通じてオプトインしている限り)iframeのスタイル
`light-dark()`または`prefers-color-scheme`を使用するSVG
`color-scheme`は何に影響しませんか?
`color-scheme`と`prefers-color-scheme`メディアクエリの間には、残念ながら断絶があります。`prefers-color-scheme`は、`color-scheme`の値に関係なく、OSの設定を反映します。ページ内トグルでダークモードを実装している場合、`prefers-color-scheme`メディアクエリを採用することはできません。
以下のコードは`color-scheme`の影響を受けません。
```html
<picture>
<source srcset="logo-dark.png" media="(prefers-color-scheme: dark)" />
<img src="logo-light.png" alt="Product logo" />
</picture>
```
背景画像を使用する以外に、`<picture>`要素に相当する`color-scheme`を参照するアプローチは残念ながらありません。
`color-scheme`が`prefers-color-scheme`メディアクエリに影響を与える例外は2つあります。iframeとSVGです。
以下は同じドキュメントを示す2つのiframeです。iframe内のドキュメントは`prefers-color-scheme`メディアクエリを使用しています。
```html
<iframe style="color-scheme: light;" src="/example.html"></iframe>
<iframe style="color-scheme: dark;" src="/example.html"></iframe>
```
上記の例からわかるように、親ドキュメントで`color-scheme: dark`が使用されている場合、`@media (prefers-color-scheme: dark)`のスタイルが適用されます。これはSVGにも同様の原則が適用されます。
以下は`.svg`ファイルのコンテンツです。
```xml
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<style>
circle {
fill: rgb(40,40,40);
}
@media (prefers-color-scheme: dark) {
circle {
fill: rgb(216, 216, 216);
}
}
</style>
<circle cx="50" cy="50" r="50" />
</svg>
```
```html
<img style="color-scheme: light;" src="/circle.svg" alt="">
<img style="color-scheme: dark;" src="/circle.svg" alt="">
```
CSS仕様は最近更新され、ドキュメントの`color-scheme`がすべてのコンテキストでメディアクエリに影響を与えるようになりましたが、どのブラウザもまだ実装していません。
Safariの注意点
SVG内での`prefers-color-scheme`メディアクエリのサポートはSafari 27で追加されましたが、`color-scheme`はメディアクエリに影響しません(バグレポート参照)。
iframe内での`prefers-color-scheme`メディアクエリのサポートはSafari 27で追加され、親ドキュメントの`color-scheme`はそれをオーバーライドします(期待通りです🎉)。しかし、他のバグも残っています(バグレポート参照)。
`light-dark()`を画像やグラデーションで使用する
`light-dark()`関数は元々色に限定されていました。現在では、グラデーションや画像にも使用できます(Chrome/Edgeバージョン150、Firefoxバージョン150、Safari Technology Preview以降)。
```css
.bg-gradient {
background-image: light-dark(linear-gradient(15deg, #b9b6ff, #308dc6), linear-gradient(15deg, #6b7495, #001339));
}
```
```html
<div class="bg-gradient" style="color-scheme: light;"></div>
<div class="bg-gradient" style="color-scheme: dark;"></div>
```
単色とグラデーションを切り替えることも可能です。
```css
.bg-grad-solid {
background-image: light-dark(linear-gradient(15deg, #b9b6ff, #308dc6), image(#001339));
}
```
画像
```css
.bg {
background-image: light-dark(url(/lightmode.avif), url(/darkmode.avif));
}
```
ライト
ダーク
システム
色、画像、グラデーション以上のものを変更する
全体として、モード間で変更する必要があるのは色ですが、例外もあります。例えば、ボックスシャドウはダークモードの暗い背景では見えにくいため、代わりに境界線(border)を適用することを決定するかもしれません。これを実装するのは現在かなり困難です。
CSS標準化団体は、CSSの`if()`ステートメントまたはスタイルクエリを使用して現在の`color-scheme`を検出する方法を追加する予定ですが、どのブラウザもこの機能は実装していません。代替策をハックすることは可能です。以下では2つのアプローチを見ていきます。
ページがダークモードを使用している場合に`true`となるCSS変数(variable)を定義します。
```css
html {
--dark: false;
}
html:has([content="light dark"]) {
@media (prefers-color-scheme: dark) {
--dark: true;
}
}
html:has([content="dark"]) {
--dark: true;
}
```
すべてのブラウザは現在、カスタムプロパティの値に応じてスタイルを適用できるスタイルクエリをサポートしています。
```css
@container style(--dark: false) {
.card {
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2);
}
}
@container style(--dark: true) {
.card {
border: solid 1px rgb(94, 94, 94);
}
}
```
スタイルクエリを使用する、より良いアプローチは次のようになります。
```css
@property --usedScheme {
syntax: "<color>";
inherits: true;
initial-value: transparent;
}
body {
--usedScheme: light-dark(white, black);
}
@container style(--usedScheme: white) {
.card {
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2);
}
}
@container style(--usedScheme: black) {
.card {
border: solid 1px rgb(94, 94, 94);
}
}
```
(可能性のある)未来:JavaScriptによる`prefers-color-scheme`のオーバーライド
将来的には、JavaScriptで`prefers-color-scheme`メディアクエリをオーバーライドできるようになるかもしれません。仕様、MDNのエントリ、Chrome Canaryのプロトタイプは存在しますが、Safariチームはこのアイデアに反対しています。