Постоянное переключение тем (темный режим) с помощью Svelte (SvelteKit) и Tailwind

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


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

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

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


Стилизация и использование переключателя

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

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 Code, попробуйте перезапустить редактор).

На этом этапе у вас должно получиться вот это:

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

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

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

tailwind.config.cjs

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

Теперь классы dark:{class} будут применяться всегда, когда класс dark присутствует ранее в дереве 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... -->
Войти в полноэкранный режим Выйти из полноэкранного режима

Что дает:

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

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

Используя API Window.mediaMatch(), мы можем обновлять значение 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 - окно не определено.

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

  • Мы добавили переключатель, который добавляет и удаляет dark как класс из корня документа.
  • Учитывая, что мы обновили tailwind.config.cjs для перехода по классам, стили подхватываются в блоке body в app.css и применяются к background-color и тексту color.
  • Мы определяем, запросил ли пользователь темный или светлый режим по умолчанию с помощью prefers-color-scheme (это функция CSS media), и обновляем состояние 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), обратитесь к документации Vite, документации SvelteKit или официальному диску Svelte.

Оцените статью
devanswers.ru
Добавить комментарий