ホームページ  >  記事  >  ウェブフロントエンド  >  自動モードの CSS テーマ セレクター [チュートリアル]

自動モードの CSS テーマ セレクター [チュートリアル]

Linda Hamilton
Linda Hamiltonオリジナル
2024-10-08 22:08:30828ブラウズ

このチュートリアルでは、Svelte でテーマ セレクターを作成し、Web サイトで複数のテーマ オプションを有効にする方法を説明します。ユーザーのデバイス設定に適応する自動テーマも含まれています。

Svelte で実装されていますが、ほとんどの機能は標準の HTML と CSS に依存しているため、他のフレームワークで簡単に複製できます。

プロジェクトのソース コードはここにあります: https://github.com/ivarnm/theme_selector

ライブデモ: https://theme-selector-inm.vercel.app/

プロジェクトのセットアップ:

既存の Svelte プロジェクトがない場合は、Svelte のスタート ガイドに従ってプロジェクトを作成できます: https://svelte.dev/docs/introduction

カラー変数を追加します。

CSS 変数を使用してカラーテーマを定義します。まず、src/styles/global.css ファイルを作成し、次の CSS を追加します:

:root {
  --neutral-0: white;
  --neutral-10: #fffcf1;
  --neutral-30: #d2d2d2;
  --neutral-40: #b7b7b7;
  --neutral-60: #666666;
  --neutral-70: #333333;
  --neutral-100: black;

  --primary-60: #696d90;
  --primary-70: #3D405B;
  --primary-80: #303349;

  --secondary-70: #5a7b6b;
  --secondary-80: #456153;
}

.dark-theme {
  --neutral-0: black;
  --neutral-10: #1a1a1a;
  --neutral-30: #3d3d3d;
  --neutral-40: #595959;
  --neutral-60: #999999;
  --neutral-70: #cccccc;
  --neutral-100: white;

  --primary-60: #979ec7;
  --primary-70: #a7aed6;
  --primary-80: #b8bfe9;

  --secondary-70: #79BEA5;
  --secondary-80: #89cfb5;
}

.warm-theme {
  --neutral-0: #fff7e0;
  --neutral-10: #ffedcc;
  --neutral-30: #ffdbb7;
  --neutral-40: #ffb89d;
  --neutral-60: #ff9473;
  --neutral-70: #ff5733;
  --neutral-100: #4d2600;

  --primary-60: #f28e2b;
  --primary-70: #d65a31;
  --primary-80: #c44536;

  --secondary-70: #e59572;
  --secondary-80: #cf6448;
}

html {
  font-family: 'Inter', sans-serif;
  background-color: var(--neutral-10);
}

body {
  margin: 0 auto;
  max-width: 870px;
  color: var(--neutral-70);
}

これはルート要素の明るいテーマのカラーパレットを定義し、暗くて暖かいテーマを追加します。どの色とテーマを選択するかは、もちろんあなた次第です。これらはほんの一例です。また、これらの変数を使用してページの背景色と本文の色のプロパティを設定する方法もわかります。

次に、src/routes/layout.svelte ファイルを追加し、次のコードを追加して CSS ファイルをグローバルにインポートします。

<script>
  import "../styles/global.css";
</script>

ドロップダウン メニューを作成します。

私はテーマ セレクターをドロップダウン メニュー内に置きたいので、使用できる再利用可能な DropdownMenu コンポーネントを作成します。同じことをしたい場合は、 src/lib/components/DropdownMenu.svelte ファイルを作成し、以下を追加します:

<script lang="ts">
  let menuOpen = false;

  const toggleMenu = () => {
    menuOpen = !menuOpen;
  };

  const handleDropdownFocusLoss = (event: FocusEvent) => {
    const focusedElement = event.relatedTarget instanceof HTMLElement ? event.relatedTarget : null;
    const menuElement = event.currentTarget instanceof HTMLElement ? event.currentTarget : null;

    // Check if the new focus is inside the menu
    if (focusedElement && menuElement && menuElement.contains(focusedElement)) {
        return;
    }

    menuOpen = false;
  };
</script>

<div class="container">
  <div class="menu" on:focusout={handleDropdownFocusLoss}>
    <button class="icon-btn" on:click={toggleMenu}>
      <slot name="icon"></slot>
    </button>

    {#if menuOpen}
      <div class="dropdown">
        <slot name="dropdown"></slot>
      </div>
    {/if}
  </div>
</div>

<style>
  .menu {
    position: relative;
  }

  .icon-btn {
    background-color: transparent;
    border: none;
    cursor: pointer;
    padding: 6px;
    margin: 0;
    border-radius: 50%;
    overflow: hidden;
    width: 49px;
    height: 49px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .icon-btn:hover {
    outline: none;
    background-color: var(--neutral-30);
  }

  .dropdown {
    position: absolute;
    top: 60px;
    right: 0;
    background-color: var(--neutral-30);
    border: 1px solid var(--neutral-40);
    color: var(--neutral-100);
    padding: 5px;
    z-index: 100;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 200px;
  }

  .dropdown :global(button) {
    padding: 10px 15px;
    font-size: 20px;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
  }

  .dropdown :global(button:hover) {
    background-color: var(--neutral-40);
  }
</style>

テーマセレクターコンポーネントを作成します。

次に、テーマの選択を処理するコンポーネントを作成します。ファイルを作成します
src/lib/components/ThemeSelector.svelte に次の内容を追加します:

<script lang="ts">
  import { onMount } from 'svelte';
  import { writable } from 'svelte/store';
  import DropdownMenu from '$lib/components/DropdownMenu.svelte';

  // If you want to use the theme variable in other components, you can move it to a dedicated ts/js file and import it here instead
  let theme = writable<string>("system");

  // Define your themes and their names.
  const THEMES = [
      { value: 'system', label: 'Automatic' },
      { value: 'light', label: 'Light' },
      { value: 'dark', label: 'Dark' },
      { value: 'warm', label: 'Warm' },
  ];

  onMount(() => {
    // Prevents the code from running on the server
    if (typeof window == 'undefined') return;

    let storedTheme = localStorage.getItem('theme');

    // Get the user's system theme preference
    let systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 

    if (storedTheme && THEMES.some(themeOption => themeOption.value === storedTheme)) {
      theme.set(storedTheme);
    } 
    else {
      theme.set('system');
      applyTheme(systemTheme);
    }

    // Update the automatic theme when the system theme changes if the theme is set to automatic
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
      systemTheme = e.matches ? 'dark' : 'light';
      if (storedTheme === 'system') {
        applyTheme(systemTheme);
      }
    });

    theme.subscribe(value => {
      if (value === 'system') {
        applyTheme(systemTheme);
      } 
      else {
        applyTheme(value);
      }

      localStorage.setItem('theme', value);
      storedTheme = value;
    });
  });

  function applyTheme(theme: string) {
    document.documentElement.className = "";
    if(theme === 'system') return;

    // Add the theme class (e.g dark-theme) to the document element to apply the theme
    document.documentElement.classList.add(`${theme}-theme`);
  }

</script>

<DropdownMenu>
  <svg slot="icon" width="37" height="37" viewBox="0 0 37 37" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M29 18.5C29 24.299 24.299 29 18.5 29C12.701 29 8 24.299 8 18.5C8 12.701 12.701 8 18.5 8C24.299 8 29 12.701 29 18.5Z" stroke="currentColor" stroke-width="2"/>
    <path d="M9 28L6.17157 30.8284" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <path d="M1 18.5H5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <path d="M9 9L6.17157 6.17157" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <path d="M18.5 5L18.5 1" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <path d="M28 9L30.8284 6.17157" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <path d="M32 18.5H36" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <path d="M28 28L30.8284 30.8284" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <path d="M18.5 36L18.5 32" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
    <circle cx="14.5" cy="18.5" r="6.5" stroke="currentColor" stroke-width="2"/>
    <line x1="8.03459" y1="22.0512" x2="17.227" y2="12.8588" stroke="currentColor" stroke-width="2"/>
    <line x1="11.0346" y1="25.0512" x2="20.227" y2="15.8588" stroke="currentColor" stroke-width="2"/>
  </svg>  

  <div slot="dropdown">
    {#each THEMES as themeOption}
      <button 
        class="theme-button" 
        class:selected={$theme === themeOption.value}
        on:click={() => $theme = themeOption.value}
      >
        <svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
          <circle cx="12" cy="12" r="7" fill="{$theme === themeOption.value ? 'var(--primary-60)' : 'transparent'}"/>
        </svg>
        <span>{themeOption.label}</span>
      </button>
    {/each}
  </div>
</DropdownMenu>

<style>
  :global(svg) {
    color: var(--neutral-70);
  }

  .theme-button.selected {
    font-weight: bold;
  }

  button {
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 4px;
    background-color: transparent;
    color: inherit;
    cursor: pointer;
  }
</style>

コンポーネントがブラウザに読み込まれるときに、ユーザーが以前にテーマを選択したかどうかを確認します。そうでない場合は、デフォルトの自動テーマが使用されます。自動テーマは、ユーザーが OS で明るい色のテーマを要求したか、暗い色のテーマを要求したかを検出するメディア クエリのprefers-color-scheme を使用して、テーマを明るいまたは暗いのいずれかに設定します。

テーマ、たとえば warm を設定すると、クラス warm-theme がページのルート要素に追加されます。これにより、CSS カラー変数がオーバーライドされます。 global.css ファイルの .warm-theme セレクターで事前に定義されています。

テーマセレクターの追加:

これで、ThemeSelector コンポーネントをレイアウト ファイルに追加できます。 src/routes/layout.svelte ファイルの内容を次のように変更します。

<script>
  import '../styles/global.css';
  import ThemeSelector from '$lib/components/ThemeSelector.svelte';
</script>

<div>
  <header>
    <ThemeSelector />
  </header>
  <main>
    <slot />
  </main>
</div>

<style>
  header {
    display: flex;
    justify-content: flex-end;
  }

  main {
    margin: 50px 0;
  }
</style>

これにより、すべてのページの右上にテーマ セレクターのドロップダウンが追加されます。

テーマを表示するには、いくつかのサンプルコンテンツを追加します。

src/routes/page.svelte ファイルに、カラーテーマを表示するボックスをいくつか追加できます。

<h1>Theme selector</h1>

<div class="box-container">
  <div class="box neutral-0">--neutral-0</div>
  <div class="box neutral-10">--neutral-10</div>
  <div class="box neutral-30">--neutral-30</div>
  <div class="box neutral-40">--neutral-40</div>
  <div class="box neutral-60">--neutral-60</div>
  <div class="box neutral-70">--neutral-70</div>
  <div class="box neutral-100">--neutral-100</div>
  <div class="box primary-60">--primary-60</div>
  <div class="box primary-70">--primary-70</div>
  <div class="box primary-80">--primary-80</div>
  <div class="box secondary-70">--secondary-70</div>
  <div class="box secondary-80">--secondary-80</div>
</div>

<style>
  .box-container {
    display: flex;
    gap: 30px;
    flex-wrap: wrap;
  }

  .box {
    width: 150px;
    height: 150px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .neutral-0 {
    background-color: var(--neutral-0);
    color: var(--neutral-100);
  }

  .neutral-10 {
    background-color: var(--neutral-10);
    color: var(--neutral-100);
    border: 5px solid var(--neutral-30);
    box-sizing: border-box;
  }

  .neutral-30 {
    background-color: var(--neutral-30);
    color: var(--neutral-100);
  }

  .neutral-40 {
    background-color: var(--neutral-40);
    color: var(--neutral-100);
  }

  .neutral-60 {
    background-color: var(--neutral-60);
    color: var(--neutral-0);
  }

  .neutral-70 {
    background-color: var(--neutral-70);
    color: var(--neutral-0);
  }

  .neutral-100 {
    background-color: var(--neutral-100);
    color: var(--neutral-0);
  }

  .primary-60 {
    background-color: var(--primary-60);
    color: var(--neutral-0);
  }

  .primary-70 {
    background-color: var(--primary-70);
    color: var(--neutral-0);
  }

  .primary-80 {
    background-color: var(--primary-80);
    color: var(--neutral-0);
  }

  .secondary-70 {
    background-color: var(--secondary-70);
    color: var(--neutral-0);
  }

  .secondary-80 {
    background-color: var(--secondary-80);
    color: var(--neutral-0);
  }
</style>

そして、これが最終結果です

CSS Theme Selector with Automatic Mode [Tutorial]

以上が自動モードの CSS テーマ セレクター [チュートリアル]の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。