🌙

個人サイトにダークモード(ダークテーマ)を実装する

2023/02/19に公開約21,700字

はじめに

これは静的な Web サイトへダークモード(ダークテーマ)を実装する方法を説明する記事です。

対象読者は、趣味で個人サイトを運営している非 IT エンジニアの方です。
特に個人サイト用テンプレートを使用して創作・同人サイトを作成しているような方(自分で一から HTML や CSS を書いていない方)を想定しています。

静的な Web サイトとは

Web サーバー上にある HTML ファイル等をそのまま配信している Web サイトです。

厳密さを無視して対象読者の方に馴染みがありそうな例で説明すると、WordPress やホームページ作成サービス(Ameba Ownd、Jimdo、Wix など)を使わず、自力で HTML ファイル等を書き換えてレンタルサーバーにアップロードしていたら大体静的な Web サイトだと思います。

記事の構成

以下の二通りの方法を、この順番で説明します。

  1. 閲覧者のシステム設定に合わせてライトモード/ダークモードを自動で適用する方法
  2. 画面上にライトモード/ダークモードを切り替えるボタンを設置し、ボタンを押したタイミングでライトモード/ダークモードを任意に切り替える方法

サンプルコード

この記事は実際に手を動かしながら学習するためのサンプルコードがあります。

https://gitlab.com/k1350/dark_mode_sample/-/releases

上記のリンク先のページで「Source code (zip)」というリンクをクリックすると zip ファイルをダウンロードします。
任意の場所に展開してご利用ください。

STEP 1. 色を CSS 変数にする

事前準備

サンプルコードをダウンロードし、任意の場所に展開してください。
展開したサンプルコード内の base/index.html をブラウザで開いてください。

サンプルテンプレート

このようなページが開きましたでしょうか。
本書では、こちらの簡素なテンプレートを題材にしてダークモードを実装していきます。

以降の作業では CSS ファイルを編集しますので、テキストエディタをご準備ください。
個人的には Visual Studio Code がおすすめですがなんでもいいです。

作業

サンプルコードの base/css/style.css をテキストエディタで開いてください。
ファイルの冒頭が以下のようになっています。

base/css/style.css
@charset "utf-8";

body {
    font-family: sans-serif;
    margin: 0;
    background-color: #f9f9f9;
    color: #444444;
}

h1,
h2,
h3,
strong {
    color: #222222;
}

色についての記述が三か所あります。bodybackground-color: #f9f9f9;color: #444444;、そして h1,h2,h3,strongcolor: #222222; です。

これを下記のように変更します。

base/css/style.css
@charset "utf-8";

body {
    --background-body: #f9f9f9;
    --text-main: #444444;
    --text-strong: #222222;
}

body {
    font-family: sans-serif;
    margin: 0;
    background-color: var(--background-body);
    color: var(--text-main);
}

h1,
h2,
h3,
strong {
    color: var(--text-strong);
}

内容の説明を簡単にします。

--background-body: #f9f9f9; は、#f9f9f9 という色に --background-body という名前を付けたという意味です。
同様に #444444 という色には --text-main#222222 という色には --text-strong という名前をつけました。
これらを body に書いておくことによって、body タグ内の全部の要素でこれらの名前を使って色を呼び出すことができます。
(ほとんどのケースで CSS を適用する対象は body タグの中なので、実質的にどこでもこれらの名前を使えます)

次に var(--background-body)--background-body という名前がついている色を呼び出すという意味です。
前述のように --background-body という名前がついている色は #f9f9f9 ですので、

body {
   --background-body: #f9f9f9;
}

body {
    background-color: var(--background-body);
}

body {
    background-color: #f9f9f9;
}

は全く同じ意味になります。

このような仕組みを CSS 変数(カスタムプロパティ)と言います。詳しく知りたい方は下記 URL をご参照ください。

https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties

さて、これだけですと単にコード量が増えて複雑になっただけのように感じるかもしれません。

しかしダークモード実装にあたっては、色の定義を一か所にまとめておかないと面倒くさい のです。
単純に個人サイト用テンプレートの色合いを変えるときのように、テキストエディタの検索・置換機能を使って一気に書き換えるというわけにはいきません。
後々楽をするために、今 CSS 変数を利用して色の定義を一か所にまとめておくのが得策です。

というわけで、同様の手順で CSS ファイル内のすべての色を CSS カスタムプロパティに変更してください。

色の命名は自分がわかりやすい名前にしていただいて差し支えありませんが、たとえば下記のような CSS ファイルができあがります。

base/css/style.css
@charset "utf-8";

body {
    --background-body: #f9f9f9;
    --background-alt: #eeeeee;
    --text-strong: #222222;
    --text-main: #444444;
    --text-muted: #888888;
    --accent: lightseagreen;
    --focus: #4a8aa1;
    --visited: violet;
}

body {
    font-family: sans-serif;
    margin: 0;
    background-color: var(--background-body);
    color: var(--text-main);
}

h1,
h2,
h3,
strong {
    color: var(--text-strong);
}

small {
    color: var(--text-muted);
}

/* 以下略 */

作業が完了したら、ブラウザで base/index.html を読み込み直してみてください。
正しくできていれば最初と全く同じ表示になっているはずです。


STEP 1 の完成品はサンプル内の step1 フォルダ内にあります。

作業内容がよくわからない方や、うまくできたか確認したい方は base/css/style.css と step1/css/style.css を見比べてみてください。

STEP 2. 閲覧者のシステム設定に応じて自動でライトモード/ダークモードを適用する

事前準備

サンプルコード内の step1/css/style.css を STEP 1 が完了した時点の style.css として使います。
テキストエディタで開いてください。

作業

さて STEP 1 が完了した時点である step1/css/style.css の冒頭は下記のようになっています。

step1/css/style.css
@charset "utf-8";

body {
    --background-body: #f9f9f9;
    --background-alt: #eeeeee;
    --text-strong: #222222;
    --text-main: #444444;
    --text-muted: #888888;
    --accent: lightseagreen;
    --focus: #4a8aa1;
    --visited: violet;
}

body {
    font-family: sans-serif;
    margin: 0;
    background-color: var(--background-body);
    color: var(--text-main);
}

/* 以下略 */

ここに、下記のように + マークでハイライトされている部分を追加してください。(+ マークは実際には記述しないでください。)

step1/css/style.css
@charset "utf-8";

body {
    --background-body: #f9f9f9;
    --background-alt: #eeeeee;
    --text-strong: #222222;
    --text-main: #444444;
    --text-muted: #888888;
    --accent: lightseagreen;
    --focus: #4a8aa1;
    --visited: violet;
}

+ @media (prefers-color-scheme: dark) {
+     body {
+         --background-body: #f9f9f9;
+         --background-alt: #eeeeee;
+         --text-strong: #222222;
+         --text-main: #444444;
+         --text-muted: #888888;
+         --accent: lightseagreen;
+         --focus: #4a8aa1;
+         --visited: violet;
+     }
+ }

body {
    font-family: sans-serif;
    margin: 0;
    background-color: var(--background-body);
    color: var(--text-main);
}

/* 以下略 */

CSS 変数を定義していた body の真下に @media (prefers-color-scheme: dark) で囲まれた body を新しく作り、その中に CSS 変数を丸ごとコピペしただけです。

@media (prefers-color-scheme: dark) で囲まれた中身は、閲覧者のシステム設定がダークモード(ダークテーマ)の場合に適用され、基本の CSS 変数の中身を全部上書きします。

試してみましょう。まず今使っているシステムの設定をダークモード(ダークテーマ)に変更してください。
設定方法は各自お調べいただきたいのですが、たとえば Windows 11(バージョン22H2)であればデスクトップ上で右クリックして「個人用設定」を選択→「色」を選択→「モードを選ぶ」で「ダーク」を選択です。

そして次に step1/index.html をブラウザで開いてください。STEP1 と同じ画面が出てきます。

次に step1/css/style.css を下記のようにしてみてください。「ここを変更」というコメントの行が変更箇所です。

step1/css/style.css
@charset "utf-8";

body {
    --background-body: #f9f9f9;
    --background-alt: #eeeeee;
    --text-strong: #222222;
    --text-main: #444444;
    --text-muted: #888888;
    --accent: lightseagreen;
    --focus: #4a8aa1;
    --visited: violet;
}

@media (prefers-color-scheme: dark) {
    body {
        --background-body: #444444; /* ここを変更 */
        --background-alt: #eeeeee;
        --text-strong: #222222;
        --text-main: #444444;
        --text-muted: #888888;
        --accent: lightseagreen;
        --focus: #4a8aa1;
        --visited: violet;
    }
}

/* 以下略 */

変更を保存したら、step1/index.html をブラウザで読み込み直してください。

このように背景が暗くなりましたか?

この状態で、ご利用のシステムの設定をライトモードにしたりダークモードにしたりしてみてください。
システムの設定が変更されると勝手に step1/index.html の色も変わると思います。(もし変わらなかったらブラウザで読み込み直してみてください)

あとは @media (prefers-color-scheme: dark) の中身をお好きなダークモードの配色にするだけで実装完了です!
お疲れ様でした!

この方法は STEP 3 の方法より遥かに簡単ですので、任意にライトモード/ダークモードを切り替えたいという強いこだわりが無い限りはこの方法を選ぶことをおすすめします。


STEP 2 の完成品はサンプル内の step2 フォルダ内にあります。

作業内容がよくわからない方や、うまくできたか確認したい方は step1/css/style.css と step2/css/style.css を見比べてみてください。

STEP 3. 画面上にライトモード/ダークモードを切り替えるボタンを設置し、ボタンを押したタイミングでライトモード/ダークモードを任意に切り替える

STEP 3 では「画面上にライトモード/ダークモードを切り替えるボタンを設置し、ボタンを押したタイミングでライトモード/ダークモードを任意に切り替える」方法を説明します。

ですがスモールステップで進めたいので STEP 3-1~STEP 3-4 の四段階に分けます。

STEP 3-1. 画面上にライトモード/ダークモードを切り替えるボタンを設置し、ボタンを押したタイミングでライトモード/ダークモードを任意に切り替える

事前準備

サンプルコード内の step2 フォルダ内を STEP 2 完了状態のファイルとして使います。

作業1. ボタンを設置

とりあえずボタンを作ります。
step2/index.html をテキストディタで開いてください。冒頭が下記のようになっています。

step2/index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>sample</title>
    <link rel="stylesheet" href="css/style.css">
</head>

<body>
    <div class="Container">
        <header class="Header">
            <h1>ダークモード実装サンプル</h1>
            <nav class="MenuContainer">
                <ul class="Menu">
                    <li class="MenuItem MenuItem--active"><a>Home</a></li>
                    <li class="MenuItem"><a href="page2.html">Page2</a></li>
                </ul>
            </nav>
        </header>
	<!-- 以下略 -->

これの <div class="Container"><header class="Header"> の間にボタンを作ります。
下記の + マークでハイライトされた部分を追加してください。(+ マークは実際には記述しないでください。)

step2/index.html
 <body>
    <div class="Container">
+        <div class="ModeButton">
+            <button type="button" aria-label="ライトモードにする" id="ModeButton--light" class="ModeButton--light">
+                ライトモードにする
+            </button>
+            <button type="button" aria-label="ダークモードにする" id="ModeButton--dark" class="ModeButton--dark">
+                ダークモードにする
+            </button>
+        </div>
        <header class="Header">
            <h1>ダークモード実装サンプル</h1>

同じボタンを step2/page2.html(サイト内のすべてのページ)にも設置してください。

作業2. ボタン位置調整

次にボタン位置について、今回は適当にページの左上あたりに置くことにします。
step2/css/style.css をテキストエディタで開き、末尾に下記を追加します。

step2/css/style.css
/* 冒頭略 */

.ModeButton {
    position: absolute;
    top: 5px;
    left: 10px;
}

step2/index.html をブラウザで開いてください。

左上にボタンができました。
実際に実装するときは画像を使うなどして見栄えの良いボタンにし、位置もきちんと調整いただければと思いますが、今回は面倒なのでこのままでいきます。

作業3. ボタンを押したときの処理を追加

次にボタンを押したときの処理を書きます。

step2 フォルダに js というフォルダを作り、その中に mode.js というファイルを新規作成してください。
mode.js の中身を下記のようにします。

step2/js/mode.js
// グローバル汚染を防ぐために即時関数を使用
(function (global) {
    "use strict";
    /**
     * ダークモードとダークモードを切り替える
     * @param {boolean} changeDark ダークモードにする場合 true
     */
    const switchMode = (changeDark) => {
        if (changeDark) {
            // body に "Dark" クラスをセットしてダークモードに切り替える
            global.document.body.classList.add("Dark");
            return;
        }
        // body から "Dark" クラスを削除してライトモードに切り替える
        global.document.body.classList.remove("Dark");
    };

    // DOMContentLoaded 時に実行する処理を登録する
    global.document.addEventListener("DOMContentLoaded", () => {
        // 「ライトモードにする」ボタンをクリック時にライトモードにするイベントを追加
        const lightButton = global.document.getElementById("ModeButton--light");
        lightButton.addEventListener("click", () => {
            switchMode(false);
        });
        // 「ダークモードにする」ボタンをクリック時にダークモードにするイベントを追加
        const darkButton = global.document.getElementById("ModeButton--dark");
        darkButton.addEventListener("click", () => {
            switchMode(true);
        });
    });
})(globalThis);

軽く何をしているかの説明をすると、ページが読み込まれたとき、さっき作ったボタンに下記の処理を追加しています。

  • 「ライトモードにする」ボタンをクリック時、body タグから "Dark" というクラスを削除する
  • 「ダークモードにする」ボタンをクリック時、body タグに "Dark" というクラスを付与する

この js ファイルを step2/index.html と step2/page2.html の両方の </body> の直前で読み込みます。

step2/index.html
<!-- 冒頭略 -->

    <script src="js/mode.js"></script>
</body>

</html>

作業4. ボタンを押したらライトモード/ダークモードが切り替わるようにする

最後に、<body> に "Dark" というクラスを付与したときはダークモード、それ以外はライトモードになるようにしましょう。

step2/css/style.css をテキストエディタで開き、

step2/css/style.css
/* システムの設定がダークモードならダークモードになる */
@media (prefers-color-scheme: dark) {
    body {
        --background-body: #444444;
        --background-alt: #222222;
        --text-strong: #ffffff;
        --text-main: #eeeeee;
        --text-muted: #aaaaaa;
        --accent: #d7d742;
        --focus: #797909;
        --visited: #00ffff;
    }
}

この部分を下記のように変更してください。

step2/css/style.css
/* Dark クラスが付与されるとダークモードになる */
body.Dark {
    --background-body: #444444;
    --background-alt: #222222;
    --text-strong: #ffffff;
    --text-main: #eeeeee;
    --text-muted: #aaaaaa;
    --accent: #d7d742;
    --focus: #797909;
    --visited: #00ffff;
}

@media (prefers-color-scheme: dark) の囲みを取り払い、bodybody.Dark にするだけです。

これで実装できました。step2/index.html をブラウザで開き直し、ボタンを押してみてください。
ボタンを押すとライトモード/ダークモードが切り替わると思います。

* * *

しかし、この実装にはまだ不足している点があります。

step2/index.html で「ダークモードにする」ボタンを押してから、画面上の「page2へのリンク」を押して step2/page2.html に画面遷移してください。

page2.html もダークモードで開いてほしいと思いますが、ライトモードで開いてしまったはずです。

今の実装に足りていないのは、「ライトモード/ダークモードを記憶して復元する機能」です。STEP 3-2 でその機能を追加します。


STEP 3-1 の完成品はサンプル内の step3-1 フォルダ内にあります。

作業内容がよくわからない方や、うまくできたか確認したい方は step2 フォルダ内のファイルと step3-1 フォルダ内のファイルを見比べてみてください。

STEP 3-2. ライトモード/ダークモードを記憶して復元する

事前準備

サンプルコード内の step3-1 フォルダ内を STEP 3-1 完了状態のファイルとして使います。

概要

ライトモード/ダークモードを記憶して復元するため、モードの選択状態を localStorage に保持した上で、ページ読み込み時に localStorage から読みだして画面に反映するようにします。

localStorage とはその名の通り、ローカル(=閲覧者が使っている端末上)にちょっとしたデータを保存する仕組みです。
これによって画面遷移してもモードを保持することが可能になります。

またブラウザを閉じてもデータが消えずに保持されますので、個人サイトを前回訪問したときにダークモードを選択していれば、次に訪問したときもダークモードで表示することができます。
(ただし前回訪問時と同一のブラウザを利用することが前提)

作業

step3-1/js/mode.js をテキストエディタで開き、下記の + マークでハイライトされた部分を mode.js に追加してください。(+ マークは実際には記述しないでください)

step3-1/js/mode.js
// グローバル汚染を防ぐために即時関数を使用
(function (global) {
    "use strict";
    /**
     * ダークモードとダークモードを切り替える
     * @param {boolean} changeDark ダークモードにする場合 true
     */
    const switchMode = (changeDark) => {
        if (changeDark) {
            // body に "Dark" クラスをセットしてダークモードに切り替える
            global.document.body.classList.add("Dark");
+           // localStorage にダークモードであるという情報を保存する
+           localStorage.setItem("dark", "on");
            return;
        }
        // body から "Dark" クラスを削除してライトモードに切り替える
        global.document.body.classList.remove("Dark");
+       // localStorage からダークモードであるという情報を削除する
+       localStorage.removeItem("dark");
    };

    // DOMContentLoaded 時に実行する処理を登録する
    global.document.addEventListener("DOMContentLoaded", () => {
+       // localStorage からダークモードであるかどうかを読み取る
+       const mode = localStorage.getItem("dark");
+       // 読み取った結果に応じてモードを設定する
+       if (mode) {
+           switchMode(true);
+       } else {
+           switchMode(false);
+       }
        // 「ライトモードにする」ボタンをクリック時にライトモードにするイベントを追加
        const lightButton = global.document.getElementById("ModeButton--light");
        lightButton.addEventListener("click", () => {
            switchMode(false);
        });
        // 「ダークモードにする」ボタンをクリック時にダークモードにするイベントを追加
        const darkButton = global.document.getElementById("ModeButton--dark");
        darkButton.addEventListener("click", () => {
            switchMode(true);
        });
    });
})(globalThis);

追加した処理は以下の内容です。

  • 「ライトモードにする」ボタンをクリック時、localStorage から "dark" というキーの値を削除する
  • 「ダークモードにする」ボタンをクリック時、localStorage に "dark" というキーの値を追加する
  • ページ読み込み時、localStorage に "dark" というキーの値があるかどうか確認し、あればダークモード、なければライトモードに切り替える

では step3-2/index.html をブラウザで開き、「ダークモードにする」ボタンを押してから、画面上の「page2へのリンク」を押して step2/page2.html に画面遷移してください。

今度は page2.html がダークモードのまま開いたと思います。

* * *

いよいよ完成に近づいてきましたが、もう少し工夫の余地があります。
ダークモードになっているときに「ダークモードにする」ボタンを表示している必要はないですし、逆もそうです。

STEP 3-3 で、ダークモードだったら「ダークモードにする」ボタンが出ないようにします。


STEP 3-2 の完成品はサンプル内の step3-2 フォルダ内にあります。

作業内容がよくわからない方や、うまくできたか確認したい方は step3-1/js/mode.js と step3-2/js/mode.js を見比べてみてください。

STEP 3-3. ライトモード/ダークモードに対応したボタンだけ表示する

事前準備

サンプルコード内の step3-2 フォルダ内を STEP 3-2 完了状態のファイルとして使います。

作業

まず step3-2/js/mode.js をテキストエディタで開き、下記の + マークでハイライトされた部分を mode.js に追加してください。(+ マークは実際には記述しないでください)

step3-2/js/mode.js
// グローバル汚染を防ぐために即時関数を使用
(function (global) {
    "use strict";
    /**
     * ダークモードとダークモードを切り替える
     * @param {boolean} changeDark ダークモードにする場合 true
     */
    const switchMode = (changeDark) => {
+       const showClass = "--show";
+       const lightButton = global.document.getElementById("ModeButton--light");
+       const darkButton = global.document.getElementById("ModeButton--dark");
        if (changeDark) {
+           // 「ダークモードにする」ボタンを非表示にする
+           darkButton.classList.remove(showClass);
+           // 「ライトモードにする」ボタンを表示する
+           lightButton.classList.add(showClass);
            // body に "Dark" クラスをセットしてダークモードに切り替える
            global.document.body.classList.add("Dark");
            // localStorage にダークモードであるという情報を保存する
            localStorage.setItem("dark", "on");
            return;
        }
+       // 「ライトモードにする」ボタンを非表示にする
+       lightButton.classList.remove(showClass);
+       // 「ダークモードにする」ボタンを表示する
+       darkButton.classList.add(showClass);
        // body から "Dark" クラスを削除してライトモードに切り替える
        global.document.body.classList.remove("Dark");
        // localStorage からダークモードであるという情報を削除する
        localStorage.removeItem("dark");
    };
// 以下略

追加した処理は以下の通りです。

  • 「ライトモードにする」ボタンをクリック時、「ダークモードにする」ボタンに "--show" クラスを付与し、「ライトモードにする」ボタンから "--show" クラスを削除する
  • 「ダークモードにする」ボタンをクリック時、「ライトモードにする」ボタンに "--show" クラスを付与し、「ダークモードにする」ボタンから "--show" クラスを削除する

次にボタンに "--show" クラスが付与されたときだけボタンを表示するようにします。

step3-2/css/style.css をテキストエディタで開き、ファイルの末尾に下記を追加します。

step3-2/css/style.css
/* 冒頭略 */

/* 基本状態はモード切替のボタンは非表示 */
.ModeButton button {
    display: none;
}

/* --show クラスが付与された場合にボタンを表示 */
.ModeButton button.--show {
    display: block;
}

これで実装できました。
step3-2/index.html をブラウザで開くと、ライトモード/ダークモードの状態に応じて表示されるボタンが切り替わっています。

* * *

もう本当に完成間近ですが、最後にもう一点問題を解決する必要があります。

実は ダークモード状態で画面遷移すると一瞬ライトモードで表示されてしまい、画面が点滅したように見えることがある という問題があります。

なぜ?

ページを表示した直後、ダークモードで表示する処理が完了するまでには僅かながら時間が必要だからです。

今まで実装していた仕組み上、ダークモードで表示する処理が完了するまでのページはライトモードで表示されています。

ライトモードで表示されている時間が目視で確認可能なほど長かった場合、一瞬ライトモードで表示されるのが見えてしまうというわけです。

この問題は Web サーバーを介してページを表示していないと正しく再現しないようです。[1]
(PC 上にあるファイルを単にダブルクリックして開いていたら Web サーバーは介していません。)

ちゃんと事象を確認してから直したいという場合は以下のいずれかの方法で確認できます。

  • 適当な無料レンタルサーバーを借り、実際にファイル一式をアップロードして確認する
  • ローカル環境で Web サーバーを起動する
    • テキストエディタとして Visual Studio Code を使っている場合、「Live Server」という拡張機能を使うと何の設定もせずクリック一つで Web サーバーが起動するのでおすすめです。(詳細な説明は省略しますので検索してみてください)

なお事象の特性上、何回か画面遷移を繰り返しても事象が確認できない場合もあります。ご了承ください……。


STEP 3-3 の完成品はサンプル内の step3-3 フォルダ内にあります。

作業内容がよくわからない方や、うまくできたか確認したい方は step3-2 フォルダ内のファイルと step3-3 フォルダ内のファイルを見比べてみてください。

STEP 3-4. 画面の点滅を防ぐ

事前準備

サンプルコード内の step3-3 フォルダ内を STEP 3-3 完了状態のファイルとして使います。

概要

まず前述の「ダークモード状態で画面遷移すると一瞬ライトモードで表示されてしまい、画面が点滅したように見えることがある」回避方針を説明します。

ダークモードで表示する処理が完了するまで、画面を描画しないようにします。

つまりやることは下記の 3 点です。

  • ページを読み込んだ直後は画面を描画しない
  • ダークモードで表示する処理が完了したら画面を描画する
  • JavaScript が動かない環境ではページを永遠に表示できなくなるのを防ぐため、JavaScript が動かない環境ではライトモードで描画する

作業1. ページを読み込んだ直後は画面を描画しない

step3-3/css/style.css をテキストエディタで開きます。
ダークモードの配色を定義した下にある body に下記の + マークでハイライトされた部分を加えます。(+ マークは実際には記述しないでください)

step3-3/css/style.css
body {
    font-family: sans-serif;
    margin: 0;
    background-color: var(--background-body);
    color: var(--text-main);
+   /* ダークモード状態で画面遷移したとき、一瞬ライトモードで表示されてしまう事象の対策として初期状態では非表示 */
+   opacity: 0;
+   visibility: hidden;
}

+ /* --show クラスが付与された時点で body を表示 */
+ body.--show {
+     opacity: 1;
+     visibility: visible;
+ }

これで <body> に "--show" というクラスを付与しない限りはページが描画されなくなりました。

作業2. ダークモードで表示する処理が完了したら画面を描画する

次に step3-3/js/mode.js をテキストエディタで開きます。
下記の + マークでハイライトされた部分を加えてください。(+ マークは実際には記述しないでください)

step3-3/js/mode.js
// 冒頭略

    // DOMContentLoaded 時に実行する処理を登録する
    global.document.addEventListener("DOMContentLoaded", () => {
        // localStorage からダークモードであるかどうかを読み取る
        const mode = localStorage.getItem("dark");
        // 読み取った結果に応じてモードを設定する
        if (mode) {
            switchMode(true);
        } else {
            switchMode(false);
        }
        // 「ライトモードにする」ボタンをクリック時にライトモードにするイベントを追加
        const lightButton = global.document.getElementById("ModeButton--light");
        lightButton.addEventListener("click", () => {
            switchMode(false);
        });
        // 「ダークモードにする」ボタンをクリック時にダークモードにするイベントを追加
        const darkButton = global.document.getElementById("ModeButton--dark");
        darkButton.addEventListener("click", () => {
            switchMode(true);
        });
+       // body を描画する(ダークモード状態で画面遷移したとき、一瞬ライトモードで表示されてしまう事象の対策)
+       global.document.body.classList.add("--show");
    });
})(globalThis);

これでライトモード/ダークモードの切り替えが終わった段階でページが描画されるようになります。

この時点で step3-3/index.html を Web サーバーを介して開き、page2.html との間で画面遷移を行っても事象が発生しなくなっているはずです。

作業3. JavaScript が動かない環境ではライトモードで描画する

ブラウザの設定で JavaScript を無効にしている人は現代では稀だとは思いますが、いないとも限りません。

今の実装では JavaScript で <body> に "--show" クラスを付与することによってページを表示させているので、JavaScript を無効にしている人はページを表示できません。

その対応のため step3-3/index.html をテキストエディタで開き、<body> の直後に下記の + マークでハイライトされた部分を加えてください。(+ マークは実際には記述しないでください。)

step3-3/index.html
 <body>
+    <noscript>
+        <!-- JavaScript が無効な環境で何も表示されなくなる問題の対策 -->
+        <style>
+            body {
+                visibility: visible;
+                opacity: 1;
+            }
+        </style>
+    </noscript>
    <div class="Container">

<noscript> は JavaScript が無効な環境でのみ読み込まれます。
ここで style.css に施した <body> 非表示の CSS を上書きすることにより、JavaScript が無効な場合も無事ページが表示されるようになります。

同じ内容を step3-3/page2.html にも追加してください。

以上で実装完了です!
お疲れさまでした。


STEP 3-4 の完成品はサンプル内の step3-4 フォルダ内にあります。

作業内容がよくわからない方や、うまくできたか確認したい方は step3-4 フォルダ内のファイルと step3-4 フォルダ内のファイルを見比べてみてください。

おわりに

個人サイトにわざわざダークモードを実装しようと思う人はあまりいないかもしれないのですが、自分が実装したので纏めておこうと思い纏めました。
どなたかの助けになれば幸いです。

わかりづらい部分や誤りなどがあればコメント等でお知らせください。

脚注
  1. 私は Windows 11 で確認しておりますが、Web サーバーを介していない場合、STEP 3-4 の回避策を適用しても事象が発生しているように見えてしまいます。 ↩︎

Discussion

ログインするとコメントできます