本教程向您展示如何在 Svelte 中创建主题选择器,为您的网站启用多个主题选项。它还包括适应用户设备设置的自动主题。
虽然它是在 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>
当组件加载到浏览器中时,我们会检查用户之前是否选择过主题。如果没有,它默认为自动主题。自动主题使用媒体查询 Preferred-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中文网其他相关文章!