Основная задумка
Хотелось бы поделиться примером, который дает посетителю возможность выбирать цветовое оформление страниц «Светлая \ Тёмная» на сайте. То есть, в данном случае пользователь сможет выбирать, удобную для своих глаз тему (светлую или тёмную). Зачастую при создании нового сайта, эта опция становиться обязательной. Именно по этой причине, я посчитал необходимым поговорить об этом в данной статье.
План статьи
Перед началом нашей работы, немного продумаем план действий. С чего начать и что сделать? Любая работа начинается с алгоритма, и это грамотный подход к делу. Иногда случается так, что человек не составив плана действий, приступает к делу и теряет много лишнего времени, на импровизацию походу работы. Часто приходиться что-то переделывать, так как появляется новое и более правильное решение. В общем-то это нормально, но все таки, хочется что-то делать сразу, более или менее грамотно (на сколько позволяют ваши знания). Напишем маленький план, для нашей статьи:
- Стартовый шаблон
- Разметка нашей кнопки
- Стили и переменные для элемента Body
- Стили для оформления кнопки
- Скрипт осуществляющий функционирование кнопки
- Готовый код
Стартовый шаблон.
Весь наш код мы реализуем в одном файле index.html, для простоты и наглядности данного примера. Начальный шаблон можно создать с помощью emet, напечатав восклицательный знак и нажав клавишу tab. Далее, вставим тег <style> и <script>, в которые, в дальнейшем поместим стили и скрипт. Вот такой начальный HTML должен у вас получиться:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>СВЕТЛАЯ/ТЁМНАЯ</title>
</head>
<style>
/* --- Здесь опишем стили --- */
</style>
<body>
<!-- --- Здесь напишем разметку --- -->
<script>
// --- Здесь напишем скрипт ---
</script>
</body>
</html>
Разметка нашей кнопки «Светлая \ Тёмная».
Кнопку, которую мы создадим, можно будет разместить в любой области вашей страницы и по желанию задать ей фиксированное позиционирование. Теперь перед тем, как создать разметку нашей кнопки, создадим вспомогательный контейнер для выравнивания содержимого с классом .container. Название класса я позаимствовал у Bootstrap.
Теперь нам нужно создать главный контейнер, в котором будет находиться тело нашего блока с кнопкой «Светлая \ Тёмная». Присвоим этому контейнеру класс .toggle-mode. В этом блоке создадим две кнопки <button>, присвоим для каждой кнопки свой id(чтобы можно было взаимодействовать с ними средствами JavaScript). Также для обоих кнопок установим атрибут [hidden] (чтобы изначально скрыть их на странице).
Внутри наших кнопок до сих пор нет никакого содержимого. Чтобы исправить это, добавим для каждой кнопки <svg> иконку и <span> с текстом. Подходящую иконку(код svg), я копирую в буфер обмена, на сайте Font Awesome, и вставляю в нужное место. В элементе <span>, заключаю информативный текст. Если вы не желаете, чтобы кнопки имели текст, можете исключить элемент <span> из разметки, и у вас останутся только иконки. Теперь мы создали вот такую разметку, для нашего блока с кнопками:
<div class="container">
<div class="toggle-mode">
<button id="LightButton" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path
d="M375.7 19.7c-1.5-8-6.9-14.7-14.4-17.8s-16.1-2.2-22.8 2.4L256 61.1 173.5 4.2c-6.7-4.6-15.3-5.5-22.8-2.4s-12.9 9.8-14.4 17.8l-18.1 98.5L19.7 136.3c-8 1.5-14.7 6.9-17.8 14.4s-2.2 16.1 2.4 22.8L61.1 256 4.2 338.5c-4.6 6.7-5.5 15.3-2.4 22.8s9.8 13 17.8 14.4l98.5 18.1 18.1 98.5c1.5 8 6.9 14.7 14.4 17.8s16.1 2.2 22.8-2.4L256 450.9l82.5 56.9c6.7 4.6 15.3 5.5 22.8 2.4s12.9-9.8 14.4-17.8l18.1-98.5 98.5-18.1c8-1.5 14.7-6.9 17.8-14.4s2.2-16.1-2.4-22.8L450.9 256l56.9-82.5c4.6-6.7 5.5-15.3 2.4-22.8s-9.8-12.9-17.8-14.4l-98.5-18.1L375.7 19.7zM269.6 110l65.6-45.2 14.4 78.3c1.8 9.8 9.5 17.5 19.3 19.3l78.3 14.4L402 242.4c-5.7 8.2-5.7 19 0 27.2l45.2 65.6-78.3 14.4c-9.8 1.8-17.5 9.5-19.3 19.3l-14.4 78.3L269.6 402c-8.2-5.7-19-5.7-27.2 0l-65.6 45.2-14.4-78.3c-1.8-9.8-9.5-17.5-19.3-19.3L64.8 335.2 110 269.6c5.7-8.2 5.7-19 0-27.2L64.8 176.8l78.3-14.4c9.8-1.8 17.5-9.5 19.3-19.3l14.4-78.3L242.4 110c8.2 5.7 19 5.7 27.2 0zM256 368a112 112 0 1 0 0-224 112 112 0 1 0 0 224zM192 256a64 64 0 1 1 128 0 64 64 0 1 1 -128 0z" />
</svg>
<span>Светлая тема</span>
</button>
<button id="DarkButton" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z" />
</svg>
<span>Тёмная тема</span>
</button>
</div>
</div>
Стили и переменные для элемента Body.
И так, после того как мы разобрались с разметкой, нам нужно написать стили для нашего модуля. Для начала мы создадим два класса .light-mode и .dark-mode. Эти классы будут хранить в себе переменные с цветами. Внутри каждого класса, создадим две переменных —text-color и —background-color, которые будут хранить в себе цвет текста и цвет фона. Вы конечно можете создать больше переменных, если вам это будет нужно, но для примера нам этого будет достаточно. Класс .light-mode содержит черный цвет для текста, белый цвет для фона, а класс .dark-mode наоборот. В дальнейшем средствами JavaScript, мы будем по очереди прикреплять эти классы к элементу body, а сейчас мы просто присвоим свойствам color и background-color переменные, объявленные в наших классах, для элемента body. Для плавного перехода добавим свойство transition. На данном этапе у нас получились вот такие строки CSS:
/* --- присвоим переменным цвета для класса светлой темы --- */
.light-mode {
--text-color: black;
--background-color: white;
}
/* --- присвоим переменным цвета для класса тёмной темы --- */
.dark-mode {
--text-color: white;
--background-color: black;
}
/* --- теперь применим эти переменные для тега body --- */
body {
background-color: var(--background-color);
color: var(--text-color);
transition: all 0.3s ease-in-out;
}
Стили для оформления кнопки.
Давайте пропишем стили для нашего блока с кнопками. Для атрибута [hidden] пропишем {display: none !important}, чтобы наши кнопки изначально были скрыты. Для контейнера с классом .toggle-mode создадим несколько переменных, которые будут отвечать за размер текста и размер иконки. Цвет и фон кнопки, будет изменяться в зависимости от текущей темы. Для элемента <button> сделаем {display: inline-flex;} и зададим {gap: 0.5rem;}, чтобы текст не слипался с иконкой. Для svg зададим height и width , а для span — зададим font-size. В совокупности у нас получатся вот такие стили:
<style>
/* --- присвоим переменным цвета для класса светлой темы --- */
.light-mode {
--text-color: black;
--background-color: white;
}
/* --- присвоим переменным цвета для класса тёмной темы --- */
.dark-mode {
--text-color: white;
--background-color: black;
}
/* --- теперь применим эти переменные для тега body --- */
body {
background-color: var(--background-color);
color: var(--text-color);
transition: all 0.3s ease-in-out;
}
/* --- вспомогательный контейнер обвертка для выравнивания --- */
.container {
max-width: 1200px;
margin: 0 auto;
padding-left: 1rem;
padding-right: 1rem;
}
/* --- стиль скрытия для атрибута hidden --- */
[hidden] {
display: none !important;
}
/* --- переменные для настроек кнопки --- */
.toggle-mode {
--font-size: 1.5rem;
--size: 1.5rem;
--transition: all 0.3s ease-in-out;
}
/* --- стили для кнопки --- */
.toggle-mode button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
border: 1px solid var(--text-color);
border-radius: 0.25rem;
background: none;
transition: var(--transition);
color: var(--text-color);
fill: var(--text-color);
}
/* --- стили для кнопки при наведении курсора --- */
.toggle-mode button:hover {
opacity: 0.7;
}
/* --- стили svg --- */
.toggle-mode svg {
width: var(--size);
height: var(--size);
}
/* --- стили для текста (ярлыка кнопки) --- */
.toggle-mode span {
font-size: var(--font-size);
}
</style>
Скрипт осуществляющий функционирование кнопки «Светлая \ Тёмная».
Перейдем к скрипту ! Скрипт напишем в виде класса, что даcт гибкость в использовании. В нашем скрипте мы будем использовать localStorage. Это хитроумное слово обозначает локальное хранилище браузера. Мы будем использовать LocalStorage для того чтобы, при перезагрузке или переходе на другую страницу, наше оформление не возвращалось в изначальное, а оставалось таким, каким его выбрал пользователь.
Что же происходит в нашем скрипте ? Сначала скрипт проверяет, существует ли сохраненное в localStorage значение. Потому что, сохранение происходит только после того, как пользователь выберет подходящую для себя тему. Если сохраненного значения не существует, тогда скрипт устанавливает класс для <body>, согласно дефолтным параметрам (установленным в скрипте), но если же значение существует, тогда скрипт устанавливает класс согласно этому значению. После того как скрипт установил правильный класс для элемента body, скрипт убирает атрибут hidden для кнопки, которая нужна для переход на другую тему. Таким образом если текущее оформление — светлое, то отображается кнопка с id=’DarkButton’, а если оформление тёмное, то с id=’LightButton’.
Теперь скрипту остается лишь отслеживать клик по кнопке. При клике происходит перезаписывание localStorage, замена класса для элемента body и перезакрепление атрибута [hidden] с одной кнопки на другую. Вот и весь алгоритм работы этого простенького скрипта. Вот такой скрипт у нас получился:
class ToggleMode {
constructor(lightButtonID, lightBodyClass, darkButtonID, darkBodyClass, bool) {
this.lightButton = document.getElementById(lightButtonID);
this.darkButton = document.getElementById(darkButtonID)
this.lightClass = lightBodyClass
this.darkClass = darkBodyClass
this.bodyNode = document.body
this.bool = bool
this.currentClass
}
toggleMode() {
// ------------ Проверяем есть ли в localStorage сохраненный класс ------------
window.onload = () => {
if (window.localStorage.getItem('bodyClass')) {
this.bodyNode.className = window.localStorage.getItem('bodyClass')
} else {
this.bodyNode.className = this.bool ? this.lightClass : this.darkClass
}
// ----- Ставим начальный класс на BODY и показываем соотвествующую кнопку -----
if (this.bodyNode.classList.contains(this.lightClass)) {
this.bodyNode.classList.remove(this.darkClass)
this.lightButton.setAttribute('hidden', '')
this.darkButton.removeAttribute('hidden')
} else {
this.bodyNode.classList.remove(this.lightClass)
this.darkButton.setAttribute('hidden', '')
this.lightButton.removeAttribute('hidden')
}
// ----- Отслеживаем клики и меняем классы BODY, кнопок и localStorage --------
this.darkButton.addEventListener('click', (event) => {
window.localStorage.setItem('bodyClass', this.darkClass)
this.currentClass = window.localStorage.getItem('bodyClass')
this.bodyNode.classList.replace(this.lightClass, this.currentClass)
this.lightButton.removeAttribute('hidden')
this.darkButton.setAttribute('hidden', '')
event.stopPropagation()
})
this.lightButton.addEventListener('click', (event) => {
window.localStorage.setItem('bodyClass', this.lightClass)
this.currentClass = window.localStorage.getItem('bodyClass')
this.bodyNode.classList.replace(this.darkClass, this.currentClass)
this.darkButton.removeAttribute('hidden')
this.lightButton.setAttribute('hidden', '')
event.stopPropagation()
})
}
}
init() {
this.toggleMode()
}
}
// ------------- Подключаем наш класс и инициализируем его ------------
let newLightMode = new ToggleMode(
'LightButton', // --- ID кнопки перейти на светлую тему ---
'light-mode', // --- Класс светлой темы для тега Body ---
'DarkButton', // --- ID кнопки перейти на тёмную тему ---
'dark-mode', // --- Класс тёмной темы для тега Body ---
true // --- если true то первым будет применен первый класс, то есть light-mode ---
// --- если false то будет применен второй класс, то есть dark-mode ---
).init()
Готовый код кнопки «Светлая \ Тёмная».
Вот данная статья и подошла к завершению и по традиции, соединим все полученные кусочки кода в один, и оценим работу полученного кода:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>СВЕТЛАЯ/ТЁМНАЯ</title>
</head>
<style>
/* --- присвоим переменным цвета для класса светлой темы --- */
.light-mode {
--text-color: black;
--background-color: white;
}
/* --- присвоим переменным цвета для класса тёмной темы --- */
.dark-mode {
--text-color: white;
--background-color: black;
}
/* --- теперь применим эти переменные для тега body --- */
body {
background-color: var(--background-color);
color: var(--text-color);
transition: all 0.3s ease-in-out;
}
/* --- вспомогательный контейнер обвертка для выравнивания --- */
.container {
max-width: 1200px;
margin: 0 auto;
padding-left: 1rem;
padding-right: 1rem;
}
/* --- стиль скрытия для атрибута hidden --- */
[hidden] {
display: none !important;
}
/* --- переменные для настроек кнопки --- */
.toggle-mode {
--font-size: 1.5rem;
--size: 1.5rem;
--transition: all 0.3s ease-in-out;
}
/* --- стили для кнопки --- */
.toggle-mode button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
border: 1px solid var(--text-color);
border-radius: 0.25rem;
background: none;
transition: var(--transition);
color: var(--text-color);
fill: var(--text-color);
}
/* --- стили для кнопки при наведении курсора --- */
.toggle-mode button:hover {
opacity: 0.7;
}
/* --- стили svg --- */
.toggle-mode svg {
width: var(--size);
height: var(--size);
}
/* --- стили для текста (ярлыка кнопки) --- */
.toggle-mode span {
font-size: var(--font-size);
}
</style>
<body>
<div class="container">
<div class="toggle-mode">
<button id="LightButton" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path
d="M375.7 19.7c-1.5-8-6.9-14.7-14.4-17.8s-16.1-2.2-22.8 2.4L256 61.1 173.5 4.2c-6.7-4.6-15.3-5.5-22.8-2.4s-12.9 9.8-14.4 17.8l-18.1 98.5L19.7 136.3c-8 1.5-14.7 6.9-17.8 14.4s-2.2 16.1 2.4 22.8L61.1 256 4.2 338.5c-4.6 6.7-5.5 15.3-2.4 22.8s9.8 13 17.8 14.4l98.5 18.1 18.1 98.5c1.5 8 6.9 14.7 14.4 17.8s16.1 2.2 22.8-2.4L256 450.9l82.5 56.9c6.7 4.6 15.3 5.5 22.8 2.4s12.9-9.8 14.4-17.8l18.1-98.5 98.5-18.1c8-1.5 14.7-6.9 17.8-14.4s2.2-16.1-2.4-22.8L450.9 256l56.9-82.5c4.6-6.7 5.5-15.3 2.4-22.8s-9.8-12.9-17.8-14.4l-98.5-18.1L375.7 19.7zM269.6 110l65.6-45.2 14.4 78.3c1.8 9.8 9.5 17.5 19.3 19.3l78.3 14.4L402 242.4c-5.7 8.2-5.7 19 0 27.2l45.2 65.6-78.3 14.4c-9.8 1.8-17.5 9.5-19.3 19.3l-14.4 78.3L269.6 402c-8.2-5.7-19-5.7-27.2 0l-65.6 45.2-14.4-78.3c-1.8-9.8-9.5-17.5-19.3-19.3L64.8 335.2 110 269.6c5.7-8.2 5.7-19 0-27.2L64.8 176.8l78.3-14.4c9.8-1.8 17.5-9.5 19.3-19.3l14.4-78.3L242.4 110c8.2 5.7 19 5.7 27.2 0zM256 368a112 112 0 1 0 0-224 112 112 0 1 0 0 224zM192 256a64 64 0 1 1 128 0 64 64 0 1 1 -128 0z" />
</svg>
<span>Светлая тема</span>
</button>
<button id="DarkButton" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z" />
</svg>
<span>Тёмная тема</span>
</button>
</div>
</div>
<script>
class ToggleMode {
constructor(lightButtonID, lightBodyClass, darkButtonID, darkBodyClass, bool) {
this.lightButton = document.getElementById(lightButtonID);
this.darkButton = document.getElementById(darkButtonID)
this.lightClass = lightBodyClass
this.darkClass = darkBodyClass
this.bodyNode = document.body
this.bool = bool
this.currentClass
}
toggleMode() {
// ------------ Проверяем есть ли в localStorage сохраненный класс ------------
window.onload = () => {
if (window.localStorage.getItem('bodyClass')) {
this.bodyNode.className = window.localStorage.getItem('bodyClass')
} else {
this.bodyNode.className = this.bool ? this.lightClass : this.darkClass
}
// ----- Ставим начальный класс на BODY и показываем соотвествующую кнопку -----
if (this.bodyNode.classList.contains(this.lightClass)) {
this.bodyNode.classList.remove(this.darkClass)
this.lightButton.setAttribute('hidden', '')
this.darkButton.removeAttribute('hidden')
} else {
this.bodyNode.classList.remove(this.lightClass)
this.darkButton.setAttribute('hidden', '')
this.lightButton.removeAttribute('hidden')
}
// ----- Отслеживаем клики и меняем классы BODY, кнопок и localStorage --------
this.darkButton.addEventListener('click', (event) => {
window.localStorage.setItem('bodyClass', this.darkClass)
this.currentClass = window.localStorage.getItem('bodyClass')
this.bodyNode.classList.replace(this.lightClass, this.currentClass)
this.lightButton.removeAttribute('hidden')
this.darkButton.setAttribute('hidden', '')
event.stopPropagation()
})
this.lightButton.addEventListener('click', (event) => {
window.localStorage.setItem('bodyClass', this.lightClass)
this.currentClass = window.localStorage.getItem('bodyClass')
this.bodyNode.classList.replace(this.darkClass, this.currentClass)
this.darkButton.removeAttribute('hidden')
this.lightButton.setAttribute('hidden', '')
event.stopPropagation()
})
}
}
init() {
this.toggleMode()
}
}
// ------------- Подключаем наш класс и инициализируем его ------------
let newLightMode = new ToggleMode(
'LightButton', // --- ID кнопки перейти на светлую тему ---
'light-mode', // --- Класс светлой темы для тега Body ---
'DarkButton', // --- ID кнопки перейти на тёмную тему ---
'dark-mode', // --- Класс тёмной темы для тега Body ---
true // --- если true то первым будет применен первый класс, то есть light-mode ---
// --- если false то будет применен второй класс, то есть dark-mode ---
).init()
</script>
</body>
</html>
PostScriptum: JavaScript является продвинутым, и одним из основных, языком веб-программирования. Он используется не только во фронтенд разработке, но и в бэкенд программировании.
Всем добра ! Задавайте вопросы и оставляйте комментарии.
DEMO