2020年9月12日土曜日

お手軽にダークモード対応 (+処方箋)

ダークモード対応は意外と面倒なのですが、少し前に以下の記事がバズっていました。
これは素晴らしいと思ったのですが、白黒反転しただけではいくつかうまくいかないケースがあります。 その処方箋についてまとめます。

1. 色が薄くて読みにくいよ!

Bootstrap や典型的ないくつかのテンプレートでは、白黒反転しただけではわずかに読みにくいケースがあります。 そこで私が最初に思い付いたのは、以下のコードです。 配色の調整がしやすいのでほとんどのサイトでおすすめできます。
html[data-theme="dark"] {
  filter: invert(1) hue-rotate(180deg) brightness(1.5);
}
しかしこの方法ではうまくいかないケースがあることに気付きました。

2. filter 付けたくないものにも付いちゃうよ!

一番わかりやすいのは、getUserMedia などでカメラ映像を canvas に書き出す時です。 たとえば遊びで QRコードリーダーを作ってみた のですが、こういった処理をすると ホラー画像ができてしまいます。 html タグに filter を掛けると filter を付けたくないタグの調整ができなくなるため、さて困りました。

応急処置としては以下のようになるでしょうか。 復元不可能になる brightness(1.5) を諦めて、canvas だけ filter を掛け直します。
html[data-theme="dark"] { filter: invert(1) hue-rotate(180deg); }
html[data-theme="dark"] canvas { filter: invert(1) hue-rotate(180deg); }

…いかにも頭の悪いコードなのですが、簡単さを追求するとこれが一番な気がします。 色が薄くてどうしても読みにくいタグがあれば、そのタグだけ brightness(1.5) などを掛ければいいと思います。 もっと良い書き方、あるでしょうか。

3. Firefox の表示がおかしいよ!

Firefox には html, body 要素に対する background-color の filter が効かない仕様があるため、 上記のプロパティを適用しても、body 以下の要素しか反転しない問題があります。 これがどういった時に問題になるかというと、ページが短く body 要素がページ全体を覆っていない時、 下側の背景が反転しておらず不格好になります。 なので、Firefox は html 要素に色指定をつける必要があります。
html[data-theme="dark"] { filter: invert(1) hue-rotate(180deg); }
@-moz-document url-prefix() { html[data-theme="dark"] { background-color:black; } }

4. prefers-color-scheme を考慮したい

以下でいけます。最初の一回だけ色が反転するくらいなら気にしないで良いかも知れません。 私は 3 + loalStorage で十分と思ってます。
html[data-theme="dark"] { filter: invert(1) hue-rotate(180deg); }
@-moz-document url-prefix() { html[data-theme="dark"] { background-color:black; } }
@media (prefers-color-scheme: dark) {
  html { filter: invert(1) hue-rotate(180deg); }
  @-moz-document url-prefix() { html { background-color:black; } }
  html[data-theme="dark"] { filter: invert(0); }
  @-moz-document url-prefix() { html[data-theme="dark"] { background-color:white; } }
}

5. body の外側を完璧にする

3, 4 で一見完璧に見えるお手軽ダークモードですが、 スマホでページを一番上/一番下まで行き、さらに上下に動かそうとした時に、 ダークモードではない時の色設定が残る問題があります。 html で全体に filter を掛けた時、body には filter が掛かるけど、 その外には掛からないみたいなそんな感じでしょうか。 body そのものに背景色を指定しないと駄目なようなので、直すのは簡単ではありません。

暫定的な解としては以下があると思います。 html ノードに属性を付与するのではなく body に theme 属性を付与します。 body 直下のテキストノードの色が変わらないなどの制約はありますが、 font-color で対応すれば一応は何とかなる。
body[data-theme="dark"] { background-color:black; }
body[data-theme="dark"] > * { filter: invert(1) hue-rotate(180deg); }

全称セレクタを使ってるのは少し気になります。 body の下は普通なら header,main,footer,template,script くらいしかないから、別に問題ないのかな。 ただ body 直下にノードを生やす系のライブラリがあると面倒なことになりそう。 3 とどちらがいいか微妙なところ。こっちのほうが良いかな。

0 件のコメント: