Closed7

ダークモードの実装について

おとのおとの

prefers-color-scheme は、CSSメディアクエリ の一つで、ユーザーのデバイスやブラウザのテーマ設定(ライトモード/ダークモード) に基づいて、ウェブサイトのスタイルを動的に変更するための機能です。

https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme

上記を利用すれば、CSSだけでライトモードとダークモードのスタイルを実装できる。ただ、これだけでは手動での切り替えができない。

おとのおとの

手動でテーマを切り替える機能を実装するには、親要素に設定するclassに応じたCSSが適用されるようにすれば、シンプルに実装できる。

junerjuner

最近だと color-scheme プロパティで設定できますね。

※ 手動(light もしくは dark を指定)もシステム側に選択させること(light dark と両方を指定)も可能 逆に UA側の上書きを防止すること(only と設定する)も可能

https://developer.mozilla.org/ja/docs/Web/CSS/color-scheme

色だけなら light-dark() 色関数がありますね。

https://developer.mozilla.org/ja/docs/Web/CSS/color_value/light-dark

現在の color-schemeの値によっての切り替えは @container の style() の拡張待ちですね。(現在はカスタムプロパティしか対応していませんが、いずれは通常のCSS宣言も対応する予定の様です。

https://developer.mozilla.org/ja/docs/Web/CSS/CSS_containment/Container_size_and_style_queries

おとのおとの

とてつもなく勉強になります!
コメント嬉しいです🤩

おとのおとの

Tailwind CSSなら、ダークモードは容易に実装できる。

// prefixとしてdark:を付与したclassを設定する
<div class="bg-white dark:bg-gray-800">
  <h1 class="text-gray-900 dark:text-white">タイトル</h1>
</div>
tailwind.config.js
module.exports = {
  // darkMode: 'media',  // OSの設定に従う場合  
  darkMode: 'selector',  // ダークモードを手動で切り替える場合
}

https://tailwindcss.com/docs/dark-mode

おとのおとの

Next.jsでTailwind CSSを利用する前提で、テーマ設定(ライトモード/ダークモード)を手動切り替える機能を実装する。

components/DarkModeToggle.tsx
"use client";

import { useEffect, useState } from "react";
import { Moon, Sun } from "lucide-react";

export const DarkModeToggle = () => {
  // テーマ設定(true: ダークモード, false: ライトモード)
  const [isDark, setIsDark] = useState<boolean | null>(null);

  // 画面が最初に表示される時に一度だけ実行される処理
  useEffect(() => {
    if (typeof window !== "undefined") {
      // ブラウザで実行されていることを確認
      const darkMode = localStorage.getItem("darkMode");
      if (darkMode === null) {
        // ブラウザに保存された設定がない場合、
        // ユーザーのPCで設定されているテーマ設定(ダークモード/ライトモード)を取得
        const query = window.matchMedia("(prefers-color-scheme: dark)");
        setIsDark(query.matches);
      } else {
        // 前回の設定が保存されている場合、それを使用
        setIsDark(darkMode === "true");
      }
    }
  }, []);

  // ボタンがクリックされた時の処理
  const toggleDarkMode = () => {
    setIsDark((prev) => {
      const newDarkMode = !prev;
      // ページ全体の見た目を切り替え
      if (newDarkMode) {
        document.documentElement.classList.add("dark");
      } else {
        document.documentElement.classList.remove("dark");
      }
      // 次回アクセス時のために設定をブラウザに保存
      localStorage.setItem("darkMode", newDarkMode.toString());
      return newDarkMode;
    });
  };

  return (
    <button
      onClick={toggleDarkMode}
      className="rounded-lg p-2 hover:bg-gray-100 dark:hover:bg-gray-800"
      aria-label="ダークモード切り替え"
    >
      <div className="h-5 w-5">
        {/* 現在のテーマ設定に応じてアイコンを切り替え */}
        {isDark === true && <Moon className="text-gray-700" />}
        {isDark === false && <Sun className="text-white" />}
      </div>
    </button>
  );
};

ボタンを押して一度適用したテーマはlocalStorageを用いて記録させ、二度目のアクセスでは初回からそのテーマを適用する機能も合わせて搭載している。

おとのおとの

初回ページアクセス時の画面のチラつき(ダークモードを設定しているのにライトモードの画面が一瞬見えてしまう)問題は以下実装で解決できる。

layout.tsx
import "./globals.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja" suppressHydrationWarning>
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              (function() {
                const isDark = localStorage.getItem('darkMode') === 'true' || (localStorage.getItem('darkMode') === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
                if (isDark) document.documentElement.classList.add('dark');
              })();
            `,
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

あえてscriptタグで設定するのがポイント。上記で設定しているjavascriptの詳細は以下参照。

(function () {
  // ダークモードの設定を確認
  const isDark =
    // ①ブラウザに保存された設定をチェック
    localStorage.getItem("darkMode") === "true" ||
    // ②保存された設定がない場合(null)は、
    //  ユーザーのシステム設定(ダークモード/ライトモード)を使用
    (localStorage.getItem("darkMode") === null &&
      window.matchMedia("(prefers-color-scheme: dark)").matches);

  // ダークモードが有効な場合、ページ全体にdarkクラスを追加
  if (isDark) document.documentElement.classList.add("dark");
})();

このスクラップは2024/12/17にクローズされました