Maison  >  Article  >  interface Web  >  Sélecteur de thème CSS avec mode automatique [Tutoriel]

Sélecteur de thème CSS avec mode automatique [Tutoriel]

Linda Hamilton
Linda Hamiltonoriginal
2024-10-08 22:08:30867parcourir

Ce tutoriel vous montre comment créer un sélecteur de thème dans Svelte, permettant plusieurs options de thème pour votre site Web. Il comprend également un thème automatique qui s'adapte aux paramètres de l'appareil de l'utilisateur.

Bien qu'elle soit implémentée dans Svelte, la plupart des fonctionnalités reposent sur les standards HTML et CSS, ce qui facilite leur réplication dans d'autres frameworks.

Le code source du projet peut être trouvé ici : https://github.com/ivarnm/theme_selector

Démo en direct : https://theme-selector-inm.vercel.app/

Mise en place du projet :

Si vous n'avez pas encore de projet Svelte existant, vous pouvez en créer un en suivant le guide de démarrage de Svelte : https://svelte.dev/docs/introduction

Ajoutez des variables de couleur :

Nous utiliserons des variables CSS pour définir nos thèmes de couleurs. Tout d'abord, créez un fichier src/styles/global.css et ajoutez le CSS suivant :

: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);
}

Cela définit une palette de couleurs pour le thème clair sur l'élément racine et ajoute un thème sombre et chaleureux. Les couleurs et les thèmes que vous souhaitez dépendent bien sûr de vous. Ce ne sont que quelques exemples. Vous voyez également comment nous pouvons utiliser ces variables pour définir la couleur d'arrière-plan de la page et la propriété color du corps.

Ensuite, ajoutez un fichier src/routes/ layout.svelte et ajoutez le code suivant pour importer le fichier CSS globalement :

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

Créez un menu déroulant :

Je préfère avoir mon sélecteur de thème dans un menu déroulant, je vais donc créer un composant DropdownMenu réutilisable que nous pouvons utiliser. Si vous souhaitez faire de même, vous pouvez créer un fichier src/lib/components/DropdownMenu.svelte et ajouter ce qui suit :

<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>

Créez le composant de sélection de thème :

Il est maintenant temps de créer le composant qui gérera la sélection du thème. Créez le fichier
src/lib/components/ThemeSelector.svelte et ajoutez ce qui suit :

<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>

Lorsque le composant est chargé dans le navigateur, nous vérifions si l'utilisateur a préalablement sélectionné un thème. Sinon, le thème automatique est utilisé par défaut. Le thème automatique définit le thème sur clair ou foncé à l'aide de la requête multimédia préfère le schéma de couleurs pour détecter si l'utilisateur a demandé des thèmes de couleurs claires ou sombres dans son système d'exploitation.

Lors de la définition d'un thème, par exemple warm, la classe warm-theme est ajoutée à l'élément racine de la page, ce qui remplacera les variables de couleur CSS par celles que nous préalablement défini dans le sélecteur .warm-theme de notre fichier global.css.

Ajout du sélecteur de thème :

Nous pouvons maintenant ajouter le composant ThemeSelector à notre fichier de mise en page. Modifiez le contenu du fichier src/routes/ layout.svelte comme suit :

<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>

Cela ajoutera le menu déroulant du sélecteur de thème en haut à droite de toutes les pages.

Ajoutez quelques exemples de contenu pour afficher les thèmes :

Dans le fichier src/routes/ page.svelte nous pouvons ajouter quelques cases pour visualiser les thèmes de couleurs :

<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>

Et voici le résultat final

CSS Theme Selector with Automatic Mode [Tutorial]

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn