Смена темы сайта через CSS-переменные

Всем привет. Меня зовут Петр Цой. Я нахожусь в поисках своей первой работы в качестве ReactJS разработчика. Есть хороший опыт самостоятельной разработки коммерческих сайтов. В качестве примера и моего резюме выступает одноименный сайт petrtcoi.com. Ссылка на GitHub.

Если вам нужен начинающий специалист, пишите!

На сайте сейчас кот. В ближайшее время сделаю приличную фотографию и размещу ее.

Почему CSS

Возможность переключать сайт между разными цветовыми схемами подразумевает возможность динамически менять стили большинства его компонентов. В минимальном варианте - это цвет текста и цвет заднего фона.

Если округлить, то добиться этого можно двумя путями:

  • через JavaScript, меняя свойства соответствующих переменных;
  • через CSS, меняя значения CSS-переменных;

Первый вариант требует перерисовки всех компонентов, связанных с новой темой. Это может негативно сказаться на производительности. К тому же, здесь потребуется дополнительная настройка проекта, так как свойства новой темы нужно передавать всем компонентам сайта.

Второй вариант не имеет данного недостатка. Мы просто меняем значения CSS-переменных, отвечающих за отображение темы, и обновление сайта происходит в автоматическом режиме без перерисовки каких-либо компонентов. Этот способ не только быстрее, но и проще в реализации.

На скриншоте ниже показан результат смены темы на моем сайте.

Внизу можно заметить, что один компонент все же переписывался - это собственно сам Switcher, который меняет тему.
Внизу можно заметить, что один компонент все же переписывался - это собственно сам Switcher, который меняет тему.

Настройка темы через CSS

Для реализации данной возможности я вынес переменные, связанные с темой в отдельный файл.

/* src/theme/theme.css */ :root { --color-text-main: #bab7b7; --background: #1b1b1b; --change-theme-duration: 0.5s; } [data-theme="light"] { --color-text-main: #1b1b1b; --background: white; }

Здесь заданы переменные для цвета текста и заднего фона. В базовом варианте - для темной темы, ниже - для светлой темы "light".

Для красоты также добавлена характеристика --change-theme-duration: 0.5s;, чтобы цвета менялись не мгновенно, но с небольшой задержкой.

Рядом, в этой же папке, разместим вспомогательный файл со списком возможных тем. У нас их две.

// src/theme/theme.enum.ts export enum Theme { light = 'light', dark = 'dark' }

Далее CSS-переменные темы прописываются в файле с глобальными стилями.

/* src/css/globals.css */ html, body { /* другие стили */ color: var(--color-text-main); background-color: var(--background); transition: background-color var(--change-theme-duration); }

Готово!

Все, что теперь требуется, так это повесить на наш компонент <ThemeSwitcher /> обработчик, который будет устанавливать на корневой элемент требуемый нам атрибут.

Результат.

Подводные камни. Часть 1.

Подобная реализация смены темы размывает зону ответственности. Получается, что часть логики приложения продолжает обрабатываться через JavaScript, а какая-то часть - уже через CSS. Возможно это создаст некоторые трудности при дальнейшем росте проекта. Тем более, если злоупотреблять данным приемом.

С другой стороны, этот метод слишком эффективен, чтобы можно было им пренебречь. Думаю, достаточно будет выполнить следующего:

  • Позже к проекту подключу Redux и подобные "глобальные" эффекты будут проводить через него. Все будет аккуратно лежать в одном месте и, по крайней мере, будет просто найти нужный участок кода.
  • Подобные CSS-трюки постараюсь ограничить рамками отдельных элементов.

Подводные камни. Часть 2.

Главное преимущество CSS-подхода привело и к сложности его тестирования. Так как стандартная библиотека для unit-тестов @testing-library попросту не "видит", какие именно параметры скрыты за названиями классов, то она и не в состоянии проверить, какой цвет установлен у элемента, находится ли он в зоне видимости и т.д.

В итоге, для тестирования смены темы я использовал e2e тестирование с помощью Playwright. Это не так удобно, как быстрые unit-тесты, но работает.

Подробнее про тестирование напишу в своей следующей записи.Спасибо за внимание.

Начать дискуссию