MaterializeCSS 2で作るダークモード・ボタン

2024/10/25   HIRANO Satoshi

MaterializeCSS 2.xを使用して、OSレベルのテーマ検出を行うダークモードボタンの実装方法を説明します。MaterializeCSSはバージョン2からMaterial Design M3への対応が進んでいます。

開発 CSS Material Design Materialize Aurelia
フォロー
シェア

このようなボタンのコンポーネントを作成します。中央のボタンは、OSのテーマ設定に従うためのものです。


MaterializeCSSのテーマ設定についてのドキュメントでは、ライト/ダークのトグルボタンの作成について説明していますが、OSのテーマ設定を検出して従う方法については触れられていません。ここでは、その実現方法を紹介します。

実装の核心

MaterializeCSSがよくできているお陰で、基本的なコードは驚くほどシンプルです。このコードは、<html>タグにtheme="light"またはtheme="dark"属性を追加するか、ユーザーがOSモードを選択した場合は属性を削除します:

if (mode === 'dark' || mode === 'light') {
document.documentElement.setAttribute('theme', mode); } else if (mode === 'os') { document.documentElement.removeAttribute('theme'); }

なぜこのコードは機能するのか?

このコードが機能する理由は、CSS変数とメディアクエリ(@media)が自動的に選択されたテーマに適応するためです。以下は、ライトモードとダークモードのCSS変数を設定するtheme.module.scssの抜粋です:

:root {
color-scheme: light; --md-sys-color-surface: var(--md-sys-color-surface-light); --md-sys-color-on-surface: var(--md-sys-color-on-surface-light); } @media (prefers-color-scheme: dark) { :root { color-scheme: dark; --md-sys-color-surface: var(--md-sys-color-surface-dark); --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark); } } :root[theme='light'] { color-scheme: light; --md-sys-color-surface: var(--md-sys-color-surface-light); --md-sys-color-on-surface: var(--md-sys-color-on-surface-light); } :root[theme='dark'] { color-scheme: dark; --md-sys-color-surface: var(--md-sys-color-surface-dark); --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark); }

変数--md-sys-color-surfaceは背景の色を表し、ライトモードでは白(#fcfcff)、ダークモードでは黒(#1a1c1e)になります。theme属性を切り替えることで、ブラウザはテーマに応じた色を自動的に選択します。

CSSにcolor-schemeを追加

両方のモード(ライトとダーク)を完全にサポートするために、CSSにcolor-scheme: light dark;を追加する必要があります:

:root {
color-scheme: light dark; }

これにより、ブラウザはユーザーやOSの設定に応じて、適切な色のスキームを自動的に適用します。

CSSはどう適用されるか

このような設定は以下のように機能します。


  • <html>タグにtheme="light"が設定されている場合、:root[theme='light']のCSS変数が適用されます。
  • theme="dark"が設定されている場合、:root[theme='dark']のCSS変数が適用されます。
  • 属性themeがなく、OSがダークモードの場合は、@media (prefers-color-scheme: dark)のCSS変数が適用されます。
  • 属性themeがなく、OSがライトモードの場合は、デフォルトの:rootのCSS変数が適用されます。

コード全体

以下が<dark-mode>コンポーネント全体のコードです。Aurelia (1)という、とても簡単に書けるフレームワークとAurelia-Materialize Bridgeを使用しています。方式自体は他のJavaScriptフレームワークでも適用可能です。

選択したモードはlocalStorageに保存され、アプリケーションの起動時にrestoreDarkMode()を呼び出すことで復元できます。.active-shadeはライトモードでもダークモードでもボタンに選択状態を表す影をつけます。





- app.html <style> :root { color-scheme: light dark; } </style> <require from="../resources/dark-mode"></require> <dark-mode></dark-mode>


- dark-mode.html

<template>
  <style>
    .active-shade .active {
      background-color: rgba(128, 128, 128, 0.2);
    }
  </style>

  <div class="center active-shade">
    <button md-button="flat: true;" click.delegate="setDarkMode('light')" class="${mode=='light' ? 'active' : ''}" title="${'dark.light' & t}">
      <i class="material-icons-outlined">light_mode</i>
    </button>

    <button md-button="flat: true;" click.delegate="setDarkMode('os')" class="${mode=='os' ? 'active' : ''}" title="${'dark.os' & t}">
      <i class="material-icons-outlined">computer</i>
    </button>

    <button md-button="flat: true;" click.delegate="setDarkMode('dark')" class="${mode=='dark' ? 'active' : ''}" title="${'dark.dark' & t}">
      <i class="material-icons-outlined">dark_mode</i>
    </button>
  </div>
</template>
- dark-mode.ts

type DarkModeType = 'light'|'os'|'dark';

/**
  Manages dark mode.
 */
export class DarkMode {
    mode: DarkModeType;

    constructor() {
        this.mode = getDarkMode();
    }

    /**
      Sets light/dark mode.

      @params mode Any of DarkModeType. If mode is not right, 'light' is used.
     */
    setDarkMode(mode: DarkModeType) {
        this.mode = setDarkMode(mode);
    }
}

/**
  Gets light/dark mode.
 */
export function getDarkMode(): DarkModeType {
    return localStorage.darkMode || 'light';
}

/**
  Sets light/dark mode.

  @params mode Any of DarkModeType. If mode is not right, 'light' is used.
  @returns A canonical mode.
 */
export function setDarkMode(mode: DarkModeType): DarkModeType {
    if (!['light', 'os', 'dark'].includes(mode)) mode = 'light';

    if (mode === 'dark' || mode === 'light') {
        document.documentElement.setAttribute('theme', mode);
    } else if (mode === 'os') {
        document.documentElement.removeAttribute('theme');
    }

    localStorage.darkMode = mode;
    return mode;
}

/**
  Restores light/dark mode from localStorage.
 */
export function restoreDarkMode() {
    setDarkMode(getDarkMode());
}

あるクラスの色をライトモードとダークモードで変えたい場合は、このようにします。

  /* for explicit light mode */
  .shaded {
      background-color: rgba(128, 128, 128, 0.15) !important;
  }

  /* for explicit dark mode */
  html[theme="dark"] .shaded {
      background-color: rgba(128, 128, 128, 0.35) !important;
  }

  /* for OS dark mode and not explicit light mode */
  @media (prefers-color-scheme: dark) {
      html:not(html[theme="light"]) .shaded,
          background-color: rgba(128, 128, 128, 0.35) !important;
      }
  }