【JS/CSS】読み込み時に点滅してしまうダークモードの対策2選

最近ではブラウザ周りの技術が進化し、JavaScriptとCSSで簡単にダークモードを構築できるようになりましたが、JavaScriptで切り替え処理を行うと、ダークモードの切り替え前に一瞬ライトモードになる点滅が起きてしまうことが多々あります。

今回はその原因と対策をまとめてみました。

なぜ点滅が起こるのか

まず、なぜ点滅が起こるのかについて考えてみます。

点滅の現象は「ダークモードに切り替わる前にライトモードが表示されてしまう」ことに起因します。

通常これはJavaScriptの切り替えプログラムが画面の描画後に実行されてしまうことによって引き起こされますが、これにはいくつかの原因があります。

原因1 切り替えプログラムがbody後半に記述されている

ブラウザは通常順番にHTMLを解釈していくため、当たり前のようですがbody後半に記載されているプログラムはそれより前のHTMLが読み込まれてから実行されます。

そのため最初ライトモードでbodyが描写されるため点滅が発生してしまいます。

対策 なるべく早めにコードが実行されるようにする

対策は比較的簡単で、bodyが読み込まれる前、つまりheadにプログラムを設置することです。

<!DOCTYPE html>
<html lang="ja">
<head>
  ...
  <script>
  //ここにダークモードに切り替えるプログラムを設置
  </script>
  ...
</head>
<body>
  ...
</body>
</html>

これでbodyが読み込まれる前にJavaScriptが実行され、レンダリングされる前にダークモードに切り替わるはずです。

しかし、これだけではまだ点滅が発生する可能性があり、それについて別のパターンを考えてみます。

原因2 CSSなど他のソースによってレンダリングブロックされている

これは、特にCSSやフォントといった比較的サイズのあるコンテンツが含まれている際に発生する問題です。

まず、ブラウザは次の順序でページをレンダリングしていきます:

  1. HTMLを上から順番にパース
  2. <style><link rel="stylesheet">などのレンダリングに関係するCSSを読み込み
  3. その時点で一時的に表示可能な画面を描写
  4. JavaScriptを読み込んで実行
  5. その後、JavaScriptの結果を受けて再描写

つまり、この3の手順でJavaScriptによってダークモードに切り替わる前のページが描写されてしまいます。

これは2の処理に時間がかかるほど顕著に表れてきます。

対策1 一時的に表示可能な画面を作らない

つまり、ダークモードになる前に描写させなければいいんです。(ゴリ押しです)

ダークモードのJavaScriptが動作するまでhtmlタグにvisibility:hidden;を追加します。

もしダークモードのJavaScriptがhtmlタグにdata-themeを追加する形式であれば、次のように実装できます。

html:not([data-theme]) {
  visibility: hidden;
}

※このあたりの処理は実装によって変更する必要があります。

なぜdisplay:noneじゃないのか

表示させないのであれば、display:noneでも十分なはずですが、この場合は適切ではありません。

なぜかというと、display:noneではレイアウト計算が停止してしまうために読み込むタイミングでスクロールが最初表示されなくなるなど画面のがたつきの要因となります。

visibility:hiddenであれば非表示にするだけでレイアウト計算は行われるため、がたつきが発生することはありません。

対策2 HTMLをダークモードの状態で返す

ブラウザのレンダリングの順序をHTMLやJavaScriptだけで完全にコントロールすることはできないため、絶対に点滅させたくない場合はサーバー側で変更してしまうのが確実な対処法となります。

例えばブラウザ側でLocal Storageを使用している場合はCookieにも同時に保存し、サーバー側(Node.jsやPHPなど)でCookieを参照できるようにしましょう。

let theme='dark';//ここはプログラムによって渡される
localStorage.setItem('theme',theme);
//Cookieにも保存
document.cookie=`theme=${theme};path=/;max-age=31536000`;

PHPを用いた例です。

htmlタグのtheme属性を変更しています。

<html data-theme="<?= (isset($_COOKIE['theme']) && in_array($_COOKIE['theme'], ['dark', 'light'])) ? $_COOKIE['theme'] : 'auto' ?>">

また、Cookieで渡されたデータを直接埋め込むことは非常に危険なため、darkとlightのみを受け付けるように記述しています。

まとめ

ダークモードの点滅は基本的に同じような原因で発生しますが、対処方法はダークモードの実装方法によって様々です。

この記事の方法でも点滅が修正できない場合、ダークモード自体の実装方法の変更を検討してみるといいかもしれません。

ひとつの参考として、当サイトではダークモードの実装方法の一例を紹介しています: