このチュートリアルでは、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 テーマ セレクター [チュートリアル]の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。