🌙

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

2023/02/19に公開

はじめに

これは静的な 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/0.2.0

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

またブラウザは Google Chrome で動作確認しています。

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-3 の三段階に分けます。

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) {
            // html に "Dark" クラスをセットしてダークモードに切り替える
            global.document.documentElement.classList.add("Dark");
            return;
        }
        // html から "Dark" クラスを削除してライトモードに切り替える
        global.document.documentElement.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);

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

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

この js ファイルを step2/index.html と step2/page2.html の両方の <head> 内で読み込みます。適当に下のほうの位置で読み込んでいいです。

step2/index.html
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width">
     <title>sample</title>
     <link rel="stylesheet" href="css/style.css">
+    <script src="js/mode.js" defer></script>
 </head>

同じ内容を step2/page2.html(サイト内のすべてのページ)にも記述してください。

<script>defer 属性を指定していることに気を付けてください。
これを指定しておかないとページの表示がちょっと遅くなります。

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

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

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

step2/css/style.css
/* 基本の設定(ライトモード) */
body {
    --background-body: #f9f9f9;
    --background-alt: #eeeeee;
    /* 中略 */
    --focus: #4a8aa1;
    --visited: violet;
}

/* システムの設定がダークモードならダークモードになる */
@media (prefers-color-scheme: dark) {
    body {
        --background-body: #444444;
        --background-alt: #222222;
        /* 中略 */
        --focus: #797909;
        --visited: #00ffff;
    }
}

この部分を下記のように変更してください。(+ が追加、- は削除。+ 記号は実際には入力しないでください。)

step2/css/style.css
/* 基本の設定(ライトモード) */
- body {
+ html {
    --background-body: #f9f9f9;
    --background-alt: #eeeeee;
    /* 中略 */
    --focus: #4a8aa1;
    --visited: violet;
}

/* Dark クラスが付与されるとダークモードになる */
- @media (prefers-color-scheme: dark) {
-    body {
+ html.Dark {
    --background-body: #444444;
    --background-alt: #222222;
    /* 中略 */
    --focus: #797909;
    --visited: #00ffff;
-   }
}
  • 最初の bodyhtml に変更する
    • htmlbody よりも上位の要素なので、CSS 変数は相変わらず body 内のどの要素でも使えます
  • @media (prefers-color-scheme: dark) の囲みを取り払い、二つ目の bodyhtml.Dark にする

です。

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

参考:なぜ body タグではなく html タグに "Dark" クラスをつけているのか?

本記事冒頭で書いた「ダークモード状態でページ遷移時に一瞬白く画面が点滅する」事象の対策のためです。

* * *

この後のステップで JavaScript を用いて動的に "Dark" クラスを付与する実装が行われます。
JavaScript で <body> にクラスをつけるためには <body> が読み込み終わるまで待たないといけないのですが、調べたところそのタイミングでのダークモード化は遅すぎることがわかりました。

よって、最も外側の要素である <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 とはその名の通り、ローカル(=閲覧者が使っている端末上)にちょっとしたデータを保存する仕組みです。
これによって画面遷移してもモードを保持することが可能になります。

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

作業1. ダークモードかどうかを保存する

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) {
             // html に "Dark" クラスをセットしてダークモードに切り替える
             global.document.documentElement.classList.add("Dark");
+            // localStorage にダークモードであるという情報を保存する
+            localStorage.setItem("dark", "on");
             return;
         }
         // html から "Dark" クラスを削除してライトモードに切り替える
         global.document.documentElement.classList.remove("Dark");
+        // localStorage からダークモードであるという情報を削除する
+        localStorage.removeItem("dark");
     };

// 以下略

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

  • ダークモードにしたとき、localStorage に "dark" というキーの値を追加する
  • ライトモードにしたとき、localStorage から "dark" というキーの値を削除する

作業2. ページ読み込み時にダークモード化する

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

step3-1/index.html
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width">
     <title>sample</title>
     <link rel="stylesheet" href="css/style.css">
+    <script>
+        (function (global) {
+            if (localStorage.getItem("dark")) {
+                global.document.documentElement.classList.add("Dark");
+            }
+        })(globalThis);
+    </script>
     <script src="js/mode.js" defer></script>
 </head>

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

  • ページ読み込み時、localStorage に "dark" というキーの値があるかどうか確認し、あればダークモードに切り替える

同じ内容を step3-1/page2.html(サイト内のすべてのページ)にも記述してください。

この追加処理が実行される前にブラウザがページを描画し始めてしまうと 「ダークモード状態でページ遷移時に一瞬白く画面が点滅する」事象が発生してしまいます。
なぜなら追加処理が実行されるまでの間はライトモードで表示されるからです。

よってできるだけ早くこの処理を実行させなければなりません。
記述位置は style.css の読み込みより後ろ、かつ他の <script> の読み込みよりは前にしてください。
(meta タグや title タグよりは後ろのほうがいいと思います)

参考:この実装にした理由

私の調べでは(サーバーサイドでの処理は実行できないという前提で)「ブラウザが最初に画面を描画するタイミング前」に確実に処理を割り込ませることはできません。
そのようなタイミングのイベントが提供されていないためです。

 

ですがブラウザは少なくとも <body> を読み始めるまではページを描画し始めないと考えられます。(描画する物が無いので)
よって <body> 開始タグに到達する前である <head> 内で同期的に処理を実行すれば、たぶん最初の描画前に処理を終えることができると考えています。

* * *

一方 <head> 内で同期的に JavaScript を実行すると最終的なページの表示が遅くなってしまいます。
したがって急がなくていい内容は mode.js に分離して遅延読み込みさせています。

 

なお style.css 読み込みより後に書いているのは、ダークモード適用状態でページを描画させるため、style.css の読み込みが完了した状態でページを描画させたいからです。
CSS 読み込みの後に <script> タグがあるかどうかで、CSS 読み込みが完了していない状態でページが描画されうるかどうかの挙動が変わります。

* * *

では 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/css/style.css をテキストエディタで開き、ファイルの末尾に下記を追加します。

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

/* ライトモードにするボタン:ライトモード時非表示 */
.ModeButton .ModeButton--light {
    display: none;
}

/* ライトモードにするボタン:ダークモード時表示 */
html.Dark .ModeButton .ModeButton--light {
    display: block;
}

/* ダークモードにするボタン:ライトモード時表示 */
.ModeButton .ModeButton--dark {
    display: block;
}

/* ダークモードにするボタン:ダークモード時非表示 */
html.Dark .ModeButton .ModeButton--dark {
    display: none;
}

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

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

おわりに

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

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

付録A: 初版の STEP 3. 最終状態と第二版の STEP 3. 最終状態の差分

index.html, step2.html

head 内

<head> 内
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width">
     <title>sample</title>
     <link rel="stylesheet" href="css/style.css">
+    <script>
+        (function (global) {
+            if (localStorage.getItem("dark")) {
+                global.document.documentElement.classList.add("Dark");
+            }
+        })(globalThis);
+    </script>
+    <script src="js/mode.js" defer></script>
 </head>
  • localStorage からダークモード状態を読み取る処理が追加されました
  • js/mode.js の読み込みが <head> 内に移動しました
    • defer 属性をつけていることに気を付けてください

body 内

<body> 内
 <body>
-    <noscript>
-        <!-- JavaScript が無効な環境で何も表示されなくなる問題の対策 -->
-        <style>
-            body {
-                visibility: visible;
-                opacity: 1;
-            }
-        </style>
-    </noscript>
     <div class="Container">
     <!-- 中略 -->
     </div>
-    <script src="js/mode.js"></script>
 </body>
  • <body> 直後の <noscript> が削除されました(そもそもデフォルト非表示にしない方針にしました)
  • </body> 直前の js/mode.js 読み込みは <head> 内に移動したので削除されました

js/mode.js

変更が非常に多いため、もし初版の mode.js をそのままご利用でしたら中身を丸ごと差し替えるほうが確実です。

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");
+            // html に "Dark" クラスをセットしてダークモードに切り替える
+            global.document.documentElement.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");
+        // html から "Dark" クラスを削除してライトモードに切り替える
+        global.document.documentElement.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);
         });
-        // body を描画する(ダークモード状態で画面遷移したとき、一瞬ライトモードで表示されてしまう事象の対策)
-        global.document.body.classList.add("--show");
     });
 })(globalThis);
  • ボタンの表示・非表示ロジックは css/style.css に一本化したので削除されました
  • body に "Dark" クラスをセットしてダークモードに切り替えるのではなく html にセットするよう変更しました
    • global.document.body ではなく global.document.documentElement を使っている点に注意してください
  • DOMContentLoaded イベント内で localStorage からダークモード状態を読み取っていた部分は <head> 内に移動したので削除されました
  • DOMContentLoaded イベント内で body を描画する部分は削除されました(そもそもデフォルト非表示にしない方針にしました)

css/style.css

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

 /* 基本の設定(ライトモード) */
- body {
+ html {
     --background-body: #f9f9f9;
     --background-alt: #eeeeee;
     /* 中略 */
     --focus: #4a8aa1;
     --visited: violet;
 }

 /* Dark クラスが付与されるとダークモードになる */
- body.Dark {
+ html.Dark {
     --background-body: #444444;
     --background-alt: #222222;
     /* 中略 */
     --focus: #797909;
     --visited: #00ffff;
 }

 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;
-}

/* 中略 */

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

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

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

+ /* ライトモードにするボタン:ライトモード時非表示 */
+ .ModeButton .ModeButton--light {
+     display: none;
+ }

+ /* ライトモードにするボタン:ダークモード時表示 */
+ html.Dark .ModeButton .ModeButton--light {
+     display: block;
+ }

+ /* ダークモードにするボタン:ライトモード時表示 */
+ .ModeButton .ModeButton--dark {
+     display: block;
+ }

+ /* ダークモードにするボタン:ダークモード時非表示 */
+ html.Dark .ModeButton .ModeButton--dark {
+     display: none;
+ }
  • body に "Dark" クラスをセットしてダークモードに切り替えるのではなく html にセットするよう変更しました
  • body をデフォルト非表示にする方針をやめたので、表示状態の制御は削除されました
  • ボタンの表示状態について、--show クラスを付与したかどうかではなくダークモード状態かどうかで切り替えるように変更しました

以上

Discussion