ダークモード
このページで分かること
- EmDashのコンポーネントが従う配色の決め方(
<html>のdark・lightクラス、クラスがなければprefers-color-scheme) - 画像フィールドでダークモード用の画像を有効にする方法(管理画面、またはシードファイルの
darkVariant)と、編集画面での選び方 Imageコンポーネントが2つの画像を出し分ける仕組み、読み込みの動作、別の配色の決め方を使う場合のCSS
このページの目次
サイトがライトとダークのどちらで表示するかは、2つの方法のどちらかで決まります。訪問者のシステムの設定か、サイトがその訪問者ごとに保存した明示的な選択です。EmDashのコンポーネントは、<html> 要素に対する1つの決まりを通じて、この両方の情報を読み取ります。このページでは、その決まり、画像フィールドにダークモード用の画像を持たせる方法、emdash/ui の Image コンポーネントでそれを描画する方法を説明します。
テーマの決まり
コンポーネントとテンプレートは、次の2つの情報をこの順番で使います。
<html>にdarkまたはlightクラスがあれば、そのクラスで配色が固定されます。クラスはシステムの設定より優先されます。- クラスがなければ、配色は
prefers-color-schemeメディアクエリに従います。
同梱のテンプレートは、明示的な選択を theme Cookieに保存し、<head> 内のインラインスクリプトで最初の描画より前に適用します。次のスクリプトはCookieを読み取ってクラスを設定し、選択が保存されていない場合は何もしません。
<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() で1回だけ定義し、配色の固定はクラスに任せます。
: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> にクラスを付けなければ、システムの設定が適用されます。
やさしい解説
WordPressではテーマごとにダークモードの作り方が異なりますが、EmDashでは決まりが1つにまとまっています。<html> に dark か light のクラスが付いていればそれに従い、付いていなければ訪問者のパソコンやスマートフォンの設定(prefers-color-scheme)に従います。訪問者が画面上で切り替えられるようにしたい場合だけ、選択をCookieに保存して、ページの表示前にクラスを付けるスクリプトを使います。切り替えボタンを置かないサイトなら、スクリプトは不要です。
ダークモード用の画像
画像フィールドには、ダークな配色用に2枚目の画像を持たせられます。編集者はメインの画像の隣でそれを選び、Image コンポーネントが訪問者の配色に合うほうを表示します。
フィールドで画像の枠を有効にする
この枠は初期状態では無効です。フィールドごとに、管理画面またはシードファイルで有効にします。
管理画面では、「コンテンツタイプ」を開き、画像フィールドを編集して、「Dark mode variant」をオンにします。
シードファイルでは、フィールドに darkVariant ウィジェットオプションを設定します。
{
"slug": "featured_image",
"label": "Featured Image",
"type": "image",
"options": { "darkVariant": true }
}
編集画面で画像を選ぶ
-
エントリーを開き、いつもどおりメインの画像を選択します。
-
画像の下にある「Add dark mode variant」をクリックし、メディアライブラリからダークモード用の画像を選びます。
-
エントリーを保存します。
ダークモード用の画像は、フィールドの値の中に darkVariant として保存されます。メインの画像を削除すると、ダークモード用の画像も一緒に削除されます。メインの画像を差し替えた場合は、ダークモード用の画像を差し替えるか削除するまで、そのまま残ります。
ダークモード用の画像を描画する
値に darkVariant が含まれている場合、Image コンポーネントは両方の画像を描画し、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 クラスが付きます。どちらも、メインの画像の alt テキスト、幅と高さの上書き、読み込みに関する属性を使います。プレースホルダーの色は、それぞれの画像のものが使われます。
渡した id はメインの画像に付きます。ダークモード用の画像には、同じ id に --dark を付けたものが付きます。つまり id="hero" を渡すと、hero と hero--dark になります。
ダークモード用の画像が別の場所(2つ目の画像フィールドなど)にある場合は、明示的に渡します。
<Image image={post.data.hero} darkVariant={post.data.hero_dark} />
読み込みの動作
初期状態では、両方の画像が遅延読み込み(lazy)になります。ブラウザーは display: none で非表示になっている遅延読み込みの画像を取得しません。そのため、訪問者がダウンロードするのは自分の配色に合う画像だけで、もう一方は配色が変わったときに読み込まれます。
priority を指定すると、両方の画像に loading="eager" と fetchpriority="high" が付き、どの配色でも両方がダウンロードされます。テーマはブラウザー側で決まるため、訪問者がどちらの画像を見るかをサーバーは判断できません。priority は、スクロールせずに見える位置にある1枚の画像にだけ使い、ほかの画像は遅延読み込みのままにします。
やさしい解説
ダークモード用の画像は、WordPressの画像フィールドにはない、EmDashの画像フィールドの機能です。管理画面でフィールドの設定をオンにすると、記事の編集画面でメインの画像の下にダークモード用の画像を選ぶボタンが表示されます。テンプレート側は、これまでどおり Image コンポーネントに画像を渡すだけで、2枚の出し分けは自動で処理されます。priority を付けると2枚とも必ずダウンロードされるため、ページ上部の1枚だけに使います。
別のテーマの決まりを使う
同梱の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 にします。