Переключатель цветовой темы
В прошлом году боги дизайна решили, что темные моды станут новым трендом. «Светлые цвета для лохов», - смеялись они, попивая мятный чай на своих электро-велосипедах или что-то в этом роде.
И поэтому каждая операционная система, приложение и даже некоторые веб-сайты внезапно должны были работать в темном режиме. К счастью, это совпало с широкой поддержкой пользовательских свойств CSS и введением нового медиазапроса prefers-color-scheme
.
Уже есть много уроков о том, как создавать темные режимы, но зачем ограничивать себя светлыми и темными? Только ситхи имеют дело с абсолютами.
Вот почему я решил создать на своем сайте новую функцию:
динамические цветовые темы! Да вместо двух цветовых схем у меня теперь десять! Это на восемь лучше, чем на предыдущем сайте!
Определить цветовые схемы
Для начала нам нужны данные. Нам нужно определить наши темы в центральном месте, чтобы было легко получить к ним доступ и редактировать. Мой сайт использует Eleventy, который позволяет мне создать простой файл JSON для этой цели:
[
{
"id": "bowser",
"name": "Bowser's Castle",
"colors": {
"primary": "#7f5af0",
"secondary": "#2cb67d",
"text": "#fffffe",
"border": "#383a61",
"background": "#16161a",
"primaryOffset": "#e068fd",
"textOffset": "#94a1b2",
"backgroundOffset": "#29293e"
}
},
{...}
]
Наши цветовые схемы - это объекты в массиве, которые теперь доступны во время сборки. Каждая тема получает name
, id
и несколько определений цвета. Части цветовой схемы зависят от вашего конкретного дизайна; В моем случае я назначил каждой теме восемь свойств.
Хорошей идеей будет дать этим свойствам логические имена вместо визуальных, таких как «light» или «muted», так как цвета варьируются от темы к теме. Я также нашел полезным определить пару «смещенных» цветов - они используются для настройки другого цвета при взаимодействии, например при наведении и так далее.
В дополнение к темам «default» и «dark», которые у меня уже были, я создал еще восемь тем. Я использовал несколько разных источников для вдохновения; больше всего мне понравились Adobe Color и happyhues .
(Кстати, все мои темы названы в честь гоночных трасс Марио Карт 64, почему бы и нет.)
Преобразовать в пользовательские свойства CSS
Чтобы фактически использовать наши цвета в CSS, нам нужны они в другом формате. Давайте создадим таблицу стилей и сделаем из них пользовательские свойства. Используя рендеринг шаблонов Eleventy, мы можем сделать это, сгенерировав файл theme.css
из данных, зациклив на все темы. Мы будем использовать макрос для вывода определения цвета для каждого.
Я написал это в шаблонизаторе Nunjucks - но вы можете сделать это и на любом другом языке.
---
permalink: '/assets/css/theme.css'
excludeFromSitemap: true
---
/*
this macro will transform the colors in the JSON data
into custom properties to use in CSS.
*/
{% macro colorscheme(colors) %}
--color-bg: {{ colors.background }};
--color-bg-offset: {{ colors.backgroundOffset }};
--color-text: {{ colors.text }};
--color-text-offset: {{ colors.textOffset }};
--color-border: {{ colors.border }};
--color-primary: {{ colors.primary }};
--color-primary-offset: {{ colors.primaryOffset }};
--color-secondary: {{ colors.secondary }};
{% endmacro %}
/*
get the "default" light and dark color schemes
to use if no other theme was selected
*/
{%- set default = themes|getTheme('default') -%}
{%- set dark = themes|getTheme('dark') -%}
/*
the basic setup will just use the light scheme
*/
:root {
{{ colorscheme(default.colors) }}
}
/*
if the user has a system preference for dark schemes,
we'll use the dark theme as default instead
*/
@media(prefers-color-scheme: dark) {
:root {
{{ colorscheme(dark.colors) }}
}
}
/*
finally, each theme is selectable through a
data-attribute on the document. E.g:
<html data-theme="bowser">
*/
{% for theme in themes %}
html[data-theme='{{ theme.id }}'] {
{{ colorscheme(theme.colors) }}
}
{% endfor %}
Использование цветов на сайте
Теперь для утомительной части - нам нужно пройтись по всем стилям сайта и заменить каждое определение цвета соответствующим пользовательским свойством. Это отличается для каждого сайта - но ваш код может выглядеть так, если он написан на SCSS:
body {
font-family: sans-serif;
line-height: $line-height;
color: $gray-dark;
}
Замените статическую переменную SCSS пользовательским свойством темы:
body {
font-family: sans-serif;
line-height: $line-height;
color: var(--color-text);
}
👉 Внимание: Пользовательские свойства поддерживаются всеми современными браузерами, но если вам необходимо поддерживать IE11 или Opera Mini, не забудьте предоставить запасной вариант.
Кстати, можно смешивать статические переменные препроцессора и пользовательские свойства - они делают разные вещи. Высота нашей линии не будет изменяться динамически.
Теперь сделать это для каждого экземпляра color
, background
, border
, fill
... Вы получаете идею.
Построение переключателя тем
Нам все еще нужен способ переключать темы без ручного редактирования разметки, хотя это не очень удобно для пользователя. Для этого нам нужен какой-то компонент пользовательского интерфейса - переключатель тем.
Генерация разметки
Структура переключателя довольно проста: это список кнопок, по одной для каждой темы. Когда кнопка нажата, мы будем менять цвета. Давайте дадим пользователю представление о том, чего ожидать, показав цвета темы в виде маленьких образцов на кнопке:
Вот шаблон для создания этой разметки. Мы будем использовать встроенные атрибуты стиля для отображения фона, текста и акцентных цветов. Кнопка также имеет свои id
в атрибуте data-theme-id
.
<ul class="themeswitcher">
{% for theme in themes %}
<li class="themeswitcher__item">
<button class="themeswitcher__btn" data-theme-id="{{ theme.id }}" style="background-color: {{ theme.colors.background }}">
<span class="themeswitcher__name" style="color: {{ theme.colors.text }}">{{ theme.name }}</span>
<span class="themeswitcher__palette">
<span class="themeswitcher__hue" style="background-color: {{ theme.colors.primary }}">{{ theme.colors.primary }}</span>
<span class="themeswitcher__hue" style="background-color: {{ theme.colors.secondary }}">{{ theme.colors.secondary }}</span>
<span class="themeswitcher__hue" style="background-color: {{ theme.colors.border }}">{{ theme.colors.border }}</span>
<span class="themeswitcher__hue" style="background-color: {{ theme.colors.text }}">{{ theme.colors.text }}</span>
<span class="themeswitcher__hue" style="background-color: {{ theme.colors.textOffset }}">{{ theme.colors.textOffset }}</span>
</span>
</button>
</li>
{% endfor %}
</ul>
Также есть некоторые стили, но я оставлю это здесь для краткости. Если вы заинтересованы в расширенной версии, вы можете найти весь код в репозитории github.
Установка темы
Последний отсутствующий фрагмент - это некоторый Javascript для обработки функциональности переключателя.
// let's make this a new class
class ThemeSwitcher {
constructor() {
// define some state variables
this.activeTheme = 'default'
this.hasLocalStorage = typeof Storage !== 'undefined'
// get all the theme buttons from before
this.themeSelectBtns = document.querySelectorAll('button[data-theme-id]')
// when clicked, get the theme id and pass it to a function
Array.from(this.themeSelectBtns).forEach((btn) => {
const id = btn.dataset.themeId
btn.addEventListener('click', () => this.setTheme(id))
})
}
}
// this whole thing only makes sense if custom properties are supported -
// so let's check for that before initializing our switcher.
if (window.CSS && CSS.supports('color', 'var(--fake-var)')) {
new ThemeSwitcher()
}
Когда кто-то переключает темы, мы берем идентификатор темы и устанавливаем его как атрибут data-theme
в документе. Это вызовет соответствующий селектор в нашем файле theme.css
, и выбранная цветовая схема будет применена.
Поскольку мы хотим, чтобы тема сохранялась, даже когда пользователь перезагружает страницу или удаляет ее, мы сохраним выбранный идентификатор в localStorage
.
setTheme(id) {
// set the theme id on the <html> element...
this.activeTheme = id
document.documentElement.setAttribute('data-theme', id)
// and save the selection in localStorage for later
if (this.hasLocalStorage) {
localStorage.setItem("theme", id)
}
}
На сайте, представленном сервером, мы могли бы вместо этого сохранить этот фрагмент данных в файле cookie и применить идентификатор темы к элементу html перед обслуживанием страницы. Так как здесь мы имеем дело со статическим сайтом, обработка на стороне сервера отсутствует, поэтому мы должны сделать небольшой обходной путь.
Мы извлечем тему из localStorage
с помощью крошечного дополнительного скрипта в заголовке документа, сразу после загрузки таблицы стилей. В отличие от остальной части Javascript, мы хотим, чтобы это выполнялось как можно раньше:
<head>
<link rel="stylesheet" href="/assets/css/main.css">
<script>
// if there's a theme id in localstorage, use it on the <html>
localStorage.getItem('theme') &&
document.documentElement.setAttribute('data-theme', localStorage.getItem('theme'))
</script>
</head>
Если сохраненная тема не найдена, сайт использует цветовую схему по умолчанию (светлую или темную, в зависимости от системных настроек пользователя).
В заключение
Таким способом вы можете создавать любое количество тем, и они не ограничиваются плоскими цветами - с некоторыми дополнительными усилиями вы можете иметь шаблоны, градиенты или даже GIF-изображения в своем дизайне. Хотя то, что вы можете, не всегда означает, что вы должны это делать, о чем свидетельствует новая тема Rainbow Road моего сайта.