ダークモード

このページ

サイトはライトとダークを2つの方法のいずれかで決定します:訪問者のシステム設定、またはサイトがその訪問者のために保存する明示的な選択です。EmDash コンポーネントは <html> 要素の1つの規約を通じて両方のシグナルを読み取ります。このページではその規約、画像フィールドにダークバリアントを設定する方法、emdash/ui の Image コンポーネントでそれをレンダリングする方法を説明します。

テーマ規約

コンポーネントとテンプレートはこの2つのシグナルをこの順序で使用します:

  1. <html> の dark または light クラスがスキームを固定します。クラスはシステム設定より優先されます。
  2. クラスがない場合、スキームは prefers-color-scheme メディアクエリに従います。

同梱のテンプレートは明示的な選択を theme クッキーに保存し、<head> のインラインスクリプトで最初の描画前に適用します。次のスクリプトはクッキーを読み取りクラスを設定し、選択が保存されていない場合は何もしません:

<script is:inline>
	(function () {
		var c = document.cookie;
		var i = c.indexOf("theme=");
		var theme = i >= 0 ? c.slice(i + 6).split(";")[0] : null;
		if (theme === "dark" || theme === "light") {
			document.documentElement.classList.add(theme);
		}
	})();
</script>

light-dark() で色を一度定義し、クラスにスキームを固定させます:

:root {
	color-scheme: light dark;
	--color-bg: light-dark(#ffffff, #0d0d0d);
	--color-text: light-dark(#1a1a1a, #ededed);
}
:root.light {
	color-scheme: light;
}
:root.dark {
	color-scheme: dark;
}

テーマスイッチャーのないサイトにはスクリプトは不要です:<html> をクラスなしのままにすれば、システム設定が適用されます。

ダーク画像バリアント

画像フィールドはダークカラースキーム用の2番目の画像を持つことができます。エディターはプライマリ画像の横でそれを選択し、Image コンポーネントは訪問者のスキームに一致するものを表示します。

フィールドでスロットを有効にする

スロットはデフォルトでオフです。フィールドごとに、管理画面またはシードファイルで有効にします。

管理画面では、Content Types を開き、画像フィールドを編集して Dark mode variant をオンにします。

シードファイルでは、フィールドに darkVariant ウィジェットオプションを設定します:

{
	"slug": "featured_image",
	"label": "Featured Image",
	"type": "image",
	"options": { "darkVariant": true }
}

エディターでバリアントを選択する

  1. エントリを開き、通常通りプライマリ画像を選択します。

  2. 画像の下にある Add dark mode variant をクリックし、メディアライブラリからダークバリアントを選択します。

  3. エントリを保存します。

バリアントはフィールド値内に darkVariant として保存されます。プライマリ画像を削除するとバリアントも削除されます。プライマリ画像を置き換えてもバリアントは、置き換えまたは削除するまで保持されます。

バリアントをレンダリングする

Image コンポーネントは値に darkVariant がある場合、両方の画像をレンダリングし、CSS で一致するものを表示します。テンプレートでは何も変わりません:

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image } from "emdash/ui";

const slug = decodeSlug(Astro.params.slug);

if (!slug) {
  return Astro.redirect("/404");
}

const { entry: post } = await getEmDashEntry("posts", slug);

if (!post) {
  return Astro.redirect("/404");
}
---

{post.data.featured_image && <Image image={post.data.featured_image} priority />}

出力には2つの <img> 要素が含まれます。プライマリ画像はクラス emdash-image--light を取得し、バリアントは emdash-image--dark を取得します。両方ともプライマリ画像の代替テキスト、幅と高さのオーバーライド、読み込み属性を使用します。それぞれ独自のプレースホルダーカラーを保持します。

渡した id はプライマリ画像に残ります。バリアントは同じ id に --dark サフィックスが付いたものを取得するため、id="hero" は hero と hero--dark を生成します。

ダーク画像が2番目の画像フィールドなど別の場所から来る場合は、明示的に渡します:

<Image image={post.data.hero} darkVariant={post.data.hero_dark} />

読み込み動作

両方の画像はデフォルトで lazy です。ブラウザは display: none で非表示になっている lazy 画像をフェッチしないため、訪問者は自分のスキームのバリアントのみをダウンロードし、もう一方はスキームが変更されたときに読み込まれます。

priority を使用すると、両方の画像が loading="eager" と fetchpriority="high" を取得し、どちらのスキームでも両方がダウンロードされます。テーマはブラウザで決定されるため、サーバーは訪問者がどちらのバリアントを見るか分かりません。ファーストビューの1つの画像に priority を使用し、他の画像は lazy のままにしてください。

別のテーマ規約を使用する

同梱の CSS はスキームに一致しないバリアントを非表示にします。そのセレクターは <html> 部分に :where() を使用するため、クラスや属性で <html> をターゲットにするあなたのルールが優先されます。

スイッチャーが data-theme のような属性を設定する場合、最も短い修正は同じコードパスから dark と light クラスも設定することです。そうでなければ、自分のスタイルシートで4つのケースをオーバーライドします:

:root[data-theme="dark"] .emdash-image--light,
:root[data-theme="light"] .emdash-image--dark {
	display: none;
}
:root[data-theme="dark"] .emdash-image--dark,
:root[data-theme="light"] .emdash-image--light {
	display: block;
}

display の値を、スタイルシートが他の場所で画像に与える値に合わせてください。例えば img を block にリセットしていない場合は inline です。