CSSでWebサイトをダークモード対応させよう

# はじめに

この記事は [NTTテクノクロス Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ntt-tx) の6日目の記事です。

こんにちは。NTTテクノクロスの鈴木雅貴です。当社製品やソリューション、受注案件を中心とした成果物をはじめとして、イベントやらコミュニケーションといった社内のあれこれを、デザインの力でよくしていくというチームのリーダをしています。個人としてはWeb標準技術まわりのスキルで生きており、[HTML5プロフェッショナル認定試験 レベル1 対策テキスト＆問題集　Ver2.0対応版](https://book.mynavi.jp/ec/products/detail/id=75672)のいち著者だったりします。と見せかけて[プロジェクト管理をテーマにしたボードゲーム『プロジェクト テーマパーク』を遊んでみた | NTTテクノクロスブログ](https://www.ntt-tx.co.jp/column/dojo_review_blog/20190819/?utm_source=qiita&utm_medium=ac2019&utm_campaign=06)のように、隙あらば遊ぼうとしています。

今回は、iOS 13やAndroid 10でも使えるようになり、利用者が増えてきたダークモード(ダークテーマとも言われますが、本記事ではダークモードに統一します)について、Webサイトでの対応方法を紹介します。

# ダークモードとは

ダークモードとは、画面の配色を黒基調のものに切り替えられる機能であったり、状態のことを指します。最近はiOSやAndroidだけでなく、Windows 10やMac OS Mojaveでもダークモードに相当するものが実装され、OSレベルで黒基調の配色を選択できるようになりました。

ダークモードのメリットとしてよく挙げられるのは以下です。

 - 画面の輝度が下がるので、目に優しい。
 - 画面の照度が下がるので、バッテリーが長持ちする。特に自発光するOLEDでは効果が大きいと言われる。
 - なんだかクールでカッコいい！

そのような理由で、利用者が増えているようです。

ただ、そのダークモードの中で白い背景のウィンドウが登場すると、眩しくてちとつらいわけです。特にWebサイト。白背景が多い。しかしWebサイト側で、OSのモードに合わせて白だの黒だのやるわけにはいかないわけで…

と思いきや、できるようになっているんです。しかもCSSで。

# CSSでのダークモード対応

CSSの機能に、Webブラウザの状態に応じてCSSを切り替えることができる、メディアクエリなるものがあります。画面幅に応じて見た目を変更したいときなどによく使われていますね。

そのメディアクエリでOSのテーマに対応できるよう、現在策定中のCSS仕様である[Media Queries Level 5](https://drafts.csswg.org/mediaqueries-5/)には、`prefers-color-scheme`なるものが盛り込まれています。

https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme ([参考日本語訳](https://triple-underscore.github.io/mediaqueries5-ja.html#prefers-color-scheme))

これを使えば、ダークモード対応はとても簡単。従来のメディアクエリ同様、`dark`の場合の色設定をCSSで書くだけです。

例えば、ダークモード使用時、Webサイト背景は黒で文字は白で表示させたい場合、CSSで以下のようにすればオッケーです。

```css
@media (prefers-color-scheme: dark) {
  body{
    background-color: #000;
    color: #fff;
  }
}
```

`perfers-color-scheme: dark`がダークモード使用時という条件です。その場合のCSS指定を書いてあげるだけです。

やることはこれだけ！

策定中ということで、気になるWebブラウザの対応状況ですが、おなじみ[Can I use](https://caniuse.com/#search=prefers-color-scheme)によると、2019年12月現在、いわゆるモダンブラウザではEdgeを除き実装されているようです。Edgeも2020.01.15リリース予定のChronium版からは使えるようになるはず。もうすぐですね！

# もちろん色以外も変更できます

これを使えば、色以外のものも変更できます。CSSですから。

たとえば画像。`picture`要素内に配置した`source`要素で`media`属性を使えば、モードにあわせて違う画像を表示できます。下の例では、普段はlight.png、ダークモードではdark.pngが表示されます。`picture`要素については[MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/picture)をご覧ください。

```html
<picture>
  <source srcset="dark.png" media="(prefers-color-scheme: dark)">
  <img src="light.png">
</picture>
```


# せっかくなのでメンテしやすいように

普段(いわゆるライトモードを想定)は白背景、黒文字、ダークモードは逆になるようなCSSを書くとなるとこんな感じになるでしょう。ライトモードでは`light`を指定します。

```css
@media (prefers-color-scheme: light) {
  body {
    background-color: #fff;
    color: #000;
  }
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #000;
    color: #fff;
  }
}
```

モードによる変更点がこの程度ならよいのですが、たいていもっと多いはず。メンテナンスは大変になるでしょう。また、新しいモードが登場した場合、また同じようなことを書くのも面倒ですよね。そこで、CSSで変数のように使えるCSS Custom Propertiesを使って変更点をまとめておくのがオススメです。CSS Custom Propertiesについては[MDN](https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties)をご覧ください。

使用例はこんな感じです。

```css
@media (prefers-color-scheme: light) {
  :root {
    --bgcolor: #fff;
    --textcolor: #000;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bgcolor: #000;
    --textcolor: #fff;
  }
}

body {
  background-color: var(--bgcolor);
  color: var(--textcolor);
}
```

この例では記述量が増えただけのように見えますが、本来のCSS記述箇所は1つだけになるので、ずいぶん読みやすくなるはずです。

# JavaScriptからアクセスしてみよう

JavaScriptからモードを取得したい場合は、従来のメディアクエリ同様に`window.matchMedia()`を使えばOKです。`window.matchMedia()`については、[MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/matchMedia)をご覧ください。

```javascript
// ダークモードかどうかを取得
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
```

モード変更時のイベントハンドラを使えば、ダークモードに変更されたときの処理を書くこともできます。

```javascript
window.matchMedia('(prefers-color-scheme: dark)')
  .addListener((e) => {
    if (e.matches) {
      // ダークモード時の処理
    } else {
      // それ以外のモードの処理
    }
  });
```

こちらも、今までのメディアクエリ同様に書くことができます。

# ダークモードで気をつけること

メディアクエリとして実装されたおかげで、ダークモード対応は技術的には難しくありません。しかし、作られるサイト側では気をつけたいポイントがいくつかありますので、それを紹介します。

## 背景色は真っ黒にしない

上の例で使っておきながらなんですが、ダークモードでの背景色に真っ黒の`#000`を使うことはおすすめしません。真っ黒は、自然界に存在しないので、見ると違和感を感じやすいです。特に制限がなければ、少し白方向に倒しておくのがよいでしょう。

また、ディスプレイがOLEDだと、真っ黒の`#000`はディスプレイが発光していない状態です。そこから違う色へ変わるためには、ディスプレイを発光させなければいけません。ディスプレイの発光は、発光したままの色変更と比較すると時間がかかるため、それがスクロール時ににじみのような違和感となることがあります。真っ黒は省エネではあるのですが、こういう現象が発生しうることを認識しておくとよいでしょう。

## 画像の彩度を落とす

暗い背景では、明るい背景と比較して画像が明るく見えます。同じ画像でも違う印象になりがちなので、気になるようなら画像の彩度を落としましょう。

ざっくり対応して楽をしたい場合は、ダークモード時、以下のように、CSSの`filter`プロパティで`grayscale()`を指定するのもよいでしょう。

```css
img {
  filter: grayscale(50%);
}
```

## 真っ黒と真っ白の組み合わせは目が疲れやすい

`#000`と`#fff`の組み合わせは、コントラスト比が一番高くなります。[Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/TR/WCAG21/)([日本語訳](https://waic.github.io/wcag21/guidelines/))的な観点では「コントラスト比はxx以上にしましょう」というチェックだけのため、それで問題ないように思えます。しかし、コントラスト比が大きすぎると光量差が大きくなるため、目が疲れやすくなると言われています(暗闇の中の明るい光のイメージですね)。ですので、その観点からも、背景色に真っ黒を使うのは避けたほうがよいでしょう。

# おわりに

今回は、CSSを使ったWebサイトのダークモード対応を紹介し、あわせて気をつけたほうがよいポイントを紹介してみました。

ダークモード対応して、Coolそして悪目立ちしないステキなサイトをつくりましょう！

7日目は @kokubo_yuichi さんです。お楽しみに！