Как реализовать переключатель тем в JavaScript
В этой статье вы узнаете, как создать переключатель тем на JavaScript. Это должно быть довольно легко сделать, но вы также можете кое-чему научиться из моего кода. Давай повеселимся.
Какие случаи нам необходимо охватить?
Один из самых простых сценариев, которые нам следует рассмотреть, — это изменение темы со светлой на темную и наоборот. Второе, что нам нужно помнить, это то, что некоторые люди предпочитают использовать те же настройки, что и в системе. Полезно для тех, кто в течение дня переключается между темной и светлой темой. Третье — сохранение пользовательских настроек; в противном случае, если вы обновите страницу, все настройки снова будут установлены по умолчанию.
Создать магазин тем
Нашей начальной функцией будет createThemeStore()
, которая будет содержать почти все. Я хочу отметить, что, возможно, это не оптимальный подход, но, эй, мы здесь пишем для развлечения.
function createThemeStore(options) {
// Initial mode
const initialMode = options.defaultMode || 'system'
// Initial state
const state = {
mode: initialMode,
systemTheme: getSystemTheme(),
theme: getThemeByMode(initialMode),
}
}
Здесь мы создаем состояние всего с тремя переменными:
- mode: представляет выбранный режим темы с возможными значениями темного, светлого или системного. Это позволяет нам определить, использовать ли тему системы или нет.
- systemTheme: содержит текущее значение темы в вашей ОС. Даже если мы выберем конкретную тему (темную или светлую), мы все равно обновляем эту переменную при изменении темы ОС, чтобы гарантировать, что мы можем правильно настроить тему, когда пользователь переключается в системный режим.
- theme: это фактическая тема, которую видит пользователь, с возможными значениями темного или светлого.
- options.defaultMode: используется для восстановления правильных настроек темы. Например, вы можете сохранить изменения темы в
localStorage
, а затем использовать ее по умолчанию, гарантируя сохранение настроек пользователя.
Добавить подписки
Когда пользователь меняет тему или обновляется тема ОС, нам нужен способ уведомить наш код. Вот тут-то и приходят на помощь подписки. Нам нужно разрешить подписку на изменения в нашем объекте состояния. Вот код, который нам в этом поможет. Помните, что сейчас мы делаем все внутри createThemeStore()
.
function createThemeStore(options) {
// ...
// Create subscriptions object to be able notify subscribers
const subscriptions = new Map()
let subscriptionId = 0 // Just a unique id for every subscriber
// A place where we send notification to all of our subscribers
function notifyAboutThemeChange(theme) {
subscriptions.forEach((notify) => {
const notificationData = {
mode: state.mode,
theme,
}
notify(notificationData) // Calls subscribed function (The example how we use it will be later)
})
}
// A function that allows to subscribe to state changes
function subscribe(callback) {
subscriptionId++
subscriptions.set(subscriptionId, callback)
state.systemTheme = getSystemTheme() // We'll define it later
if (state.mode === 'system') {
notifyAboutThemeChange(state.systemTheme)
} else {
notifyAboutThemeChange(state.theme)
}
return subscriptionId
}
// A function that allows to unsubscribe from changes
function usubscribe(subscriptionId) {
subscriptions.delete(subscriptionId)
}
return {
subscribe,
usubscribe,
}
}
Вот как это работает со стороны потребителя.
// Create a theme store
const store = createThemeStore()
// Suscribe to changes
const subscriptionId = store.subscribe((newTheme) => {
// Here you'll be seeing theme changes
console.log(newTheme)
})
// When you need to unsubscribe from theme change, you just call
store.usubscribe(subscriptionId)
Определить настройки системной темы
Теперь, когда у нас есть базовая структура кода, давайте добавим что-нибудь полезное. Нам нужно определить две вспомогательные функции:
getSystemTheme()
: это должно вернуть текущую тему ОС, темную или светлую.
getThemeByMode()
: должен возвращать либо темный, либо светлый цвет в зависимости от режима нашей темы. Например, если установлен тёмный режим, мы возвращаем тёмный. Однако, когда для режима установлен системный, мы проверяем системную тему и отвечаем либо темной, либо светлой, в зависимости от предпочтений ОС.
Важно отметить, что этот код не будет внутри нашей функции createThemeStore()
. Мы используем window.matchMedia
с медиа-запросом «предпочитаемая цветовая схема», позволяющим нам определить, установлена ли в системе ОС темный цвет или нет.
const mediaQuery = '(prefers-color-scheme: dark)'
// Get's current OS system
function getSystemTheme() {
if (window.matchMedia(mediaQuery).matches) {
return 'dark'
}
return 'light'
}
// Based on user's preferences we return correct theme
function getThemeByMode(mode) {
if (mode === 'system') {
return getSystemTheme()
}
return mode
}
function createThemeStore(options) {
// ...
}
Теперь единственное, что нам нужно сделать, чтобы обнаружить изменения темы нашей ОС, — это добавить прослушиватель событий.
function createThemeStore(options) {
// ...
// When the OS preference has changed
window.matchMedia(mediaQuery).addEventListener('change', (event) => {
const prefersDarkMode = event.matches
// We change system theme
state.systemTheme = prefersDarkMode ? 'dark' : 'light'
// And if user chose `system` mode we notify about the change
// in order to be able switch theme when OS settings has changed
if (state.mode === 'system') {
notifyAboutThemeChange(state.systemTheme)
}
})
}
Добавьте возможность вручную менять режим темы.
Мы реализовали автоматическое обновление тем всякий раз, когда меняются настройки нашей ОС. Единственное, что мы еще не обсудили, — это ручное обновление режима темы. Вы будете использовать эту функцию для кнопок темной, светлой и системной темы.
function createThemeStore(options) {
// ...
function changeThemeMode(mode) {
const newTheme = getThemeByMode(mode)
state.mode = mode
state.theme = newTheme
if (state.mode === 'system') {
// If the mode is system, send user a system theme
notifyAboutThemeChange(state.systemTheme)
} else {
// Otherwise use the one that we've selected
notifyAboutThemeChange(state.theme)
}
}
return {
subscribe,
usubscribe,
changeThemeMode,
}
}
Пример использования
Наш код — это чистый JavaScript, и вы можете использовать его где угодно. Я продемонстрирую пример на React, но вы можете попробовать его в любом фреймворке или библиотеке, которые вам нравятся.
// Create a theme store from saved theme mode
// or use `system` if user hasn't changed preferences
const store = createThemeStore({
defaultMode: localStorage.getItem("theme") || "system",
});
function MyComponent() {
// Initial active theme is `null` here, but you could use the actual value
const [activeTheme, setActiveTheme] = useState(null)
useEffect(() => {
// Subscribe to our store changes
const subscriptionId = store.subscribe((notification) => {
// Update theme
setActiveTheme(notification.theme)
// Save selected theme mode to localStorage
localStorage.setItem('theme', notification.mode)
})
return () => {
store.usubscribe(subscriptionId)
}
}, [])
return (
<>
<p>
Active theme: <b>{activeTheme}</b>
</p>
<p>Change theme to:</p>
<button onClick={() => store.changeThemeMode("dark")}>Dark</button>
<button onClick={() => store.changeThemeMode("light")}>Light</button>
<button onClick={() => store.changeThemeMode("system")}>System</button>
<>
)
}
Спасибо!
Я ценю, что вы присоединились ко мне в этом путешествии, и если вам удалось заставить его работать, я горжусь вами! Если что-то у вас не работает или вы хотите найти весь код, вы можете найти его здесь.