Краткое примечание. Это руководство было создано со следующими версиями зависимостей: "@sveltejs/kit": "next" & "tailwindcss": "^3.1.8". Если что-то не работает должным образом, подумайте об обновлении. В качестве альтернативы, если ваши версии впереди, проверьте, какие критические изменения были внесены с тех пор.


Предварительная конфигурация

В этом уроке мы будем использовать SvelteKit. Если у вас еще нет настроенного проекта, вы можете начать здесь (рекомендую Skeleton + TypeScript).

Если вы не настроили Tailwind и не добавили его в app.css файла, есть простые инструкции на документы.


Стилизация и использование Switch

Теперь, когда все настроено. Мы можем начать с создания переключателя…

src/lib/ThemeSwitch/ThemeSwitch.svelte

<div>
    <input type="checkbox" id="theme-toggle" />
    <label for="theme-toggle" />
</div>

<style lang="postcss">
    #theme-toggle {
        @apply invisible;
    }

    #theme-toggle + label {
        @apply inline-block cursor-pointer h-12 w-12 absolute top-6 right-24 rounded-full duration-300 content-[''];
    }

    #theme-toggle:not(:checked) + label {
        @apply bg-amber-400;
    }

    #theme-toggle:checked + label {
        @apply bg-transparent;
        box-shadow: inset -18px -16px 1px 1px #ddd;
    }
</style>

Войти в полноэкранный режим

Выйти из полноэкранного режима

… и используя его:

src/routes/+page.svelte (или где-либо еще, где вы можете его потреблять, например. +layout.svelte)

<script lang="ts">
    import ThemeSwitch from '$lib/ThemeSwitch/ThemeSwitch.svelte';

    import '../app.css';
</script>

<ThemeSwitch />
<h1>Demo</h1>
Войти в полноэкранный режим

Выйти из полноэкранного режима

(Примечание. Если вы получаете сообщение об ошибке ссылки $lib как и я, и использую код Visual Studio, попробуйте перезапуск вашего редактора.)

На данный момент у вас должно быть это:

Переключение переключателя

Переопределение темного режима Tailwind

Чтобы максимально упростить интеграцию темного режима, Tailwind включает темный вариант, который позволяет вам по-разному оформлять сайт при включенном темном режиме, например: class="bg-white dark:bg-slate-800".

Это круто, но мы хотим переопределить системные настройки по умолчанию и вручную переключать темный режим. Это означает, что нам нужно внести следующие изменения:

tailwind.config.cjs

module.exports = {
  darkMode: 'class',
  // ...
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

В настоящее время, dark:{class} классы будут применяться всякий раз, когда темный класс присутствует ранее в дереве HTML, а не на основе prefers-color-scheme.

Вы можете убедиться, что это работает, добавив class="dark" к открытию html отметить в src/app.htmlа затем добавить следующее:

src/app.css (Вы также можете использовать global тег стиля)

body {
  @apply bg-white dark:bg-black text-black dark:text-white text-center;
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Это должно дать нам это (все еще нефункциональный переключатель, но темный режим включен):

Переключение переключателя, темный режим включен

⚠️ Не забудьте удалить class="dark" с открытия html тег ⚠️

Добавление функциональности коммутатору

Пришло время добавить некоторые функции к этому бесполезному переключателю. Мы хотим повторить то, что мы сделали, добавив/удалив dark класс из корня документа.

Нам нужно взять под контроль checked стоимость input и действовать что-то on:click:

<script lang="ts">
    let darkMode = true;

    function handleSwitchDarkMode() {
        darkMode = !darkMode;

        darkMode
            ? document.documentElement.classList.add('dark')
            : document.documentElement.classList.remove('dark');
    }
</script>

<div>
    <input checked={darkMode} on:click={handleSwitchDarkMode} type="checkbox" id="theme-toggle" />
    <label for="theme-toggle" />
</div>
<!-- Styles... -->
Войти в полноэкранный режим

Выйти из полноэкранного режима

Который дает:

Переключение между темным и светлым режимом

⚠️ Примечание. Если на вашем устройстве не установлен темный режим, как у меня, ваше поведение по умолчанию будет отличаться от моего — это будет исправлено в ближайшее время ⚠️

Установка значений по умолчанию

Использование Окно.mediaMatch() API, мы можем обновить darkMode значение при нагрузке, в зависимости от того, что prefers-color-scheme устройство настроено на:

<script lang="ts">
    import { browser } from '$app/env';

    let darkMode = true;

    function handleSwitchDarkMode() {
        darkMode = !darkMode;

        darkMode
            ? document.documentElement.classList.add('dark')
            : document.documentElement.classList.remove('dark');
    }

    if (browser) {
        if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
            document.documentElement.classList.add('dark');
            darkMode = true;
        } else {
            document.documentElement.classList.remove('dark');
            darkMode = false;
        }
    }
</script>

<!-- html block... -->

<!-- style block... -->
Войти в полноэкранный режим

Выйти из полноэкранного режима

Возможно, вы заметили if (browser)...это просто потому, что мы хотим убедиться, что код выполняется только на клиенте (а не на сервере), иначе приложение выдаст ошибку 500 - window is not defined.

Итак, подведем итог:

  • Мы добавили переключатель, который добавляет и удаляет dark как класс из корня документа.
  • Учитывая, что мы обновили tailwind.config.cjs идти по классам, стили подбираются в body блокировать внутри app.css и применяется к background-color и текст color.
  • Мы определяем, запросил ли пользователь темный или светлый режим по умолчанию с помощью предпочитает цветовую схему (который является CSS мультимедийная функция), и мы обновляем darkMode состояние (которое требуется переключателем) и изменение classList соответственно.

Нам нужно сделать еще одну вещь…

Включение сохраняемости с помощью локального хранилища

Когда мы переключаем переключатель, мы хотим установить значение в локальном хранилище:

localStorage.setItem('theme', darkMode ? 'dark' : 'light');

Это позволит нам проверить, theme ключ существует, и если он существует и соответствует строке dark, мы знаем, что это положительное совпадение, и можем добавить класс и обновить переменную. Если нет, то делаем наоборот:

<script lang="ts">
    import { browser } from '$app/environment';

    let darkMode = true;

    function handleSwitchDarkMode() {
        darkMode = !darkMode;

        localStorage.setItem('theme', darkMode ? 'dark' : 'light');

        darkMode
            ? document.documentElement.classList.add('dark')
            : document.documentElement.classList.remove('dark');
    }

    if (browser) {
        if (
            localStorage.theme === 'dark' ||
            (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
        ) {
            document.documentElement.classList.add('dark');
            darkMode = true;
        } else {
            document.documentElement.classList.remove('dark');
            darkMode = false;
        }
    }
</script>
Войти в полноэкранный режим

Выйти из полноэкранного режима

Добавим завершающий штрих к body чтобы мы могли получить менее резкий переход между двумя темами:

app.css

body {
    transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
    @apply bg-white dark:bg-black text-black dark:text-white text-center;
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Теперь давайте посмотрим на это в действии:

Переключение с сохранением локального хранилища

Вы также можете выполнить инициализацию в <svelte:head> чтобы избежать FOUC (Flash of Unstyled Content).

Вот и все! Тема ('dark' | 'light') теперь сохраняется при обновлении! 🪄

Исходный код можно найти здесь.

Если вам нужна дополнительная информация по вышеуказанному или что-то еще (например, добавление новых сред, таких как staging) проверить Быстрые документы, документы SvelteKit или официальный Стройный диссонанс.