Подробное руководство: создание адаптивного сайдбара для COTONTI CMF
Версия для публичной статьи – без полных листингов, но с подробным разбором ключевых моментов.
Введение
В этой статье мы шаг за шагом разберём, как превратить обычную боковую панель в современный адаптивный сайдбар, который на широких экранах всегда видим, а на мобильных устройствах плавно выезжает по нажатию кнопки-гамбургера. Все решения основаны на реальном проекте «COTONTI CMF» – сервисе мониторинга обменников. Мы не просто скопируем код, а поймём логику каждого решения, чтобы вы могли применить её в своих проектах.
К концу чтения вы будете точно знать:
- Как правильно спроектировать HTML-структуру для гибкого сайдбара.
- Как с помощью CSS-переменных и медиазапросов реализовать адаптивность и переключение тем.
- Как разделить JavaScript на независимые модули, чтобы не запутаться.
- Как обрабатывать открытие/закрытие сайдбара, блокировать прокрутку и работать с оверлеем.
Всё внимание мы уделим именно сайдбару – его разметке, стилизации и логике поведения. Второстепенной, но важной частью станет переключение светлой и тёмной темы, потому что оно напрямую влияет на внешний вид сайдбара. Остальные модули (вкладки, кнопки обмена, демо-поиск) упомянем кратко – они не являются фокусом статьи.
1. Архитектура и общая концепция
Прежде чем писать код, давайте представим, как должна работать страница.
На десктопе (ширина экрана больше 1024 пикселей) мы видим:
- Шапку с логотипом, навигацией и кнопками.
- Под шапкой – основную область, разделённую на две части: слева сайдбар шириной 300px, справа – основной контент.
- Сайдбар приклеен к верху и не скроллится вместе со страницей – он остаётся на месте благодаря
position: sticky. - Гамбургер (три полоски) скрыт.
На мобильном устройстве (ширина ≤1024px) поведение меняется:
- Шапка становится компактнее, навигация исчезает, появляется гамбургер.
- Сайдбар больше не часть потока – он фиксируется поверх контента, но изначально сдвинут за левый край экрана.
- При нажатии на гамбургер сайдбар плавно выезжает слева, а основной контент затемняется полупрозрачным оверлеем.
- Закрыть сайдбар можно тремя способами: кнопкой «Свернуть», кликом по оверлею или клавишей Escape.
- После закрытия сайдбар уезжает обратно, оверлей исчезает, прокрутка страницы восстанавливается.
Дополнительно вся страница поддерживает две цветовые схемы – тёмную (по умолчанию) и светлую. Выбор сохраняется в localStorage.
Именно такой UX мы и будем воплощать. Ключевая идея – один HTML-код, а всё поведение определяют CSS-медиазапросы и небольшой JavaScript-модуль. Никакого дублирования сайдбара для мобильной и десктопной версий.
2. Подготовка файловой структуры
Для удобства поддержки мы разнесём код по отдельным файлам:
project/
index.html
css/
styles.css
js/
theme.js
sidebar.js
tabs.js
swap.js
search-demo.js
Основные герои нашей статьи – index.html, css/styles.css и js/sidebar.js. Файлы theme.js и tabs.js мы затронем в контексте сайдбара. Остальные (swap.js, search-demo.js) оставим без подробного разбора – они решают локальные задачи и не влияют на адаптивность.
3. HTML-разметка: каркас и ключевые элементы
Начнём с index.html. Он содержит всю семантическую структуру, но ни строчки стилей или скриптов (кроме внешних библиотек Bootstrap и Font Awesome).
3.1. Шапка и гамбургер
Шапка (<header class="header">) разделена на три логические части:
- Левая: кнопка-гамбургер + логотип.
- Центральная: навигационное меню (скрыто на мобильных с помощью классов
d-none d-lg-flex). - Правая: кнопка темы и кнопка «Войти».
Код гамбургера:
<button class="hamburger" id="hamburgerBtn">
<i class="fas fa-bars"></i>
</button>
Изначально гамбургер скрыт через CSS (display: none), и только медиазапрос для ширины ≤1024px делает его видимым. Именно на этот элемент мы повесим обработчик клика для открытия/закрытия сайдбара.
3.2. Overlay – затемнение фона
Сразу после шапки, но до основного макета, идёт пустой <div>:
<div class="sidebar-overlay" id="sidebarOverlay"></div>
Он будет показан только в мобильной версии, когда сайдбар открыт. В десктопном режиме он постоянно скрыт (display: none). Его клик также будет закрывать сайдбар.
3.3. Основной контейнер layout и сайдбар
Главный контейнер <div class="layout"> объединяет сайдбар и основной контент. Это флекс-контейнер, который в десктопе выстраивает детей в строку, а на мобильных – в колонку.
Сам сайдбар (<aside class="sidebar" id="sidebar">) содержит несколько внутренних блоков:
- Чипсы (кнопки-вкладки) «Все валюты» и «Избранное».
- Поисковый блок с двумя полями и кнопкой обмена местами.
- Контейнер со списком валют.
- Заглушка для пустого избранного.
- Кнопка «Свернуть» (показывается только на мобильных).
Важнейший идентификатор id="sidebar" позволяет JavaScript легко найти элемент.
Обратите внимание на начальный класс visible у заглушки избранного – мы используем его для управления видимостью через JS.
3.4. Основной контент и подвал
В <main class="main-content"> находится всё, что справа от сайдбара: заголовки, калькулятор, блог. Вёрстка калькулятора здесь не принципиальна для темы сайдбара.
Подвал стандартный, прижимается к низу страницы благодаря флексам.
4. CSS: стилизация и адаптивность сайдбара
Все стили собраны в css/styles.css. Мы сгруппировали их по смысловым блокам и снабдили комментариями. Разберём ключевые моменты, отвечающие за поведение сайдбара.
4.1. CSS-переменные и темы
В самом начале файла мы определяем переменные для тёмной и светлой тем. Переключение основано на атрибуте data-bs-theme у элемента <html>:
:root,
[data-bs-theme="dark"] {
--sidebar-bg: #1b1e22;
/* ... */
}
[data-bs-theme="light"] {
--sidebar-bg: #f8f9fa;
/* ... */
}
Сам сайдбар использует переменную:
.sidebar {
background: var(--sidebar-bg);
/* ... */
}
Таким образом, смена атрибута автоматически обновляет фон и другие цвета без перезагрузки страницы.
4.2. Базовые стили сайдбара для десктопа
По умолчанию сайдбар ведёт себя как sticky:
.sidebar {
width: 300px;
position: sticky;
top: 56px; /* высота шапки */
height: calc(100vh - 56px);
overflow-y: auto;
transition: transform 0.3s ease;
/* ... */
}
Почему sticky, а не fixed? Потому что нам нужно, чтобы сайдбар участвовал в потоке flex-контейнера и не перекрывал контент. Sticky работает именно так: элемент ведёт себя как обычный блок, пока не достигнет указанного top, а затем «прилипает». В результате контент справа никогда не заезжает под сайдбар.
4.3. Медиазапрос: мобильная версия
При ширине экрана 1024px и меньше срабатывает медиазапрос, который полностью переопределяет поведение сайдбара:
@media (max-width: 1024px) {
.sidebar {
position: fixed;
top: 56px;
left: 0;
height: calc(100vh - 56px);
transform: translateX(-100%);
width: 280px;
z-index: 1060;
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-close-mobile { display: block; }
.layout { flex-direction: column; }
/* ... */
}
Что здесь происходит:
- Сайдбар становится
fixed, чтобы находиться поверх контента. - Изначально он сдвинут за левый край на 100% своей ширины с помощью
transform: translateX(-100%). Это скрывает его визуально. - Когда JavaScript добавляет класс
open, применяется transform: translateX(0), и сайдбар плавно выезжает. Плавность обеспечивается transition: transform 0.3s ease, заданным в базовых стилях. - Увеличиваем
z-index до 1060 (оверлей имеет 1050), чтобы сайдбар был поверх затемнения. - Кнопка закрытия, ранее скрытая (
display: none), теперь показывается. - Основной контейнер
layout переключается на вертикальное направление – контент уходит вниз.
Таким образом, один и тот же HTML-элемент адаптируется под разные устройства исключительно за счёт CSS.
4.4. Overlay
Оверлей изначально скрыт и активируется добавлением класса active через JavaScript:
.sidebar-overlay {
display: none;
position: fixed;
top: 56px; left: 0;
width: 100%; height: calc(100% - 56px);
background: rgba(0,0,0,0.4);
z-index: 1050;
}
.sidebar-overlay.active {
display: block;
}
При клике на него мы убираем класс active, что скрывает затемнение.
4.5. Дополнительный адаптив для очень узких экранов
Второй медиазапрос (max-width: 600px) перестраивает поля калькулятора в столбик. К сайдбару это прямого отношения не имеет, но показывает гибкость подхода.
5. JavaScript: логика управления сайдбаром
Теперь перейдём к тому, что заставляет сайдбар открываться и закрываться. Код находится в js/sidebar.js и представляет собой чистый модуль без зависимостей от других скриптов.
5.1. Инициализация и поиск элементов
Всё начинается после полной загрузки DOM:
document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const hamburgerBtn = document.getElementById('hamburgerBtn');
const closeBtn = document.getElementById('sidebarCloseBtn');
if (!sidebar || !hamburgerBtn) return;
// ... функции и обработчики
});
Мы проверяем наличие ключевых элементов и, если их нет, просто выходим – это позволяет использовать скрипт на страницах без сайдбара.
5.2. Функции открытия и закрытия
function openSidebar() {
sidebar.classList.add('open');
if (overlay) overlay.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeSidebar() {
sidebar.classList.remove('open');
if (overlay) overlay.classList.remove('active');
document.body.style.overflow = '';
}
Логика проста:
- Добавляем/убираем класс
open – именно он запускает CSS-анимацию выезда. - Добавляем/убираем класс
active у оверлея. - Блокируем/разблокируем прокрутку страницы через
overflow у body. Это важно, чтобы под оверлеем контент не скроллился.
5.3. Обработчики событий
На гамбургер вешается функция toggleSidebar, которая переключает состояние:
hamburgerBtn.addEventListener('click', () => {
sidebar.classList.contains('open') ? closeSidebar() : openSidebar();
});
Кнопка закрытия и клик по оверлею вызывают closeSidebar().
Дополнительно мы слушаем нажатие клавиши Escape на всей странице:
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && sidebar.classList.contains('open')) {
closeSidebar();
}
});
Так сайдбар можно закрыть без использования мыши – это улучшает доступность.
5.4. Обработчик ресайза – важная деталь
Представьте: пользователь открыл сайдбар на мобильном (ширина 600px), а затем повернул устройство или изменил размер окна до 1200px. Если ничего не предпринять, сайдбар останется в состоянии open, но так как медиазапрос уже не применяет transform, сайдбар встанет на своё обычное место и будет видим. Однако body останется с overflow: hidden, и страница перестанет скроллиться. Чтобы избежать этого, мы добавили:
window.addEventListener('resize', () => {
if (window.innerWidth > 1024) {
closeSidebar();
}
});
При переходе в десктопный режим мы принудительно закрываем сайдбар, убираем оверлей и восстанавливаем прокрутку. Это делает интерфейс надёжным.
5.5. Почему мы не используем Bootstrap для сайдбара?
Многие проекты применяют offcanvas-компонент Bootstrap, но в нашем случае требовалось более гибкое решение: сайдбар на десктопах должен быть именно sticky, а не просто скрытым. Стандартный offcanvas всегда является fixed и требует дополнительной обёртки. Наш подход легче и даёт полный контроль над анимацией и поведением.
6. Переключение темы и его связь с сайдбаром
Хотя переключение темы не основная тема статьи, оно напрямую влияет на сайдбар, поэтому разберём ключевые моменты.
Задача модуля theme.js – управлять атрибутом data-bs-theme на элементе <html> и сохранять выбор в localStorage. Почему именно <html>, а не body? Потому что CSS-переменные, отвечающие за цвета, привязаны к этому атрибуту:
[data-bs-theme="light"] {
--sidebar-bg: #f8f9fa;
}
Когда пользователь нажимает кнопку темы, мы считываем текущий атрибут, инвертируем его и обновляем:
html.setAttribute('data-bs-theme', newTheme);
localStorage.setItem('index-mono-theme', newTheme);
Всё! Все элементы, включая сайдбар, мгновенно перекрашиваются благодаря переменным.
В начальный момент мы проверяем сохранённое значение или системные предпочтения, чтобы сразу установить нужную тему без мигания. Для этого в <html> заранее прописан data-bs-theme="dark", а JS, если надо, меняет его на light. Это гарантирует, что до выполнения скрипта страница будет тёмной.
Иконка кнопки (луна/солнце) также обновляется в функции setTheme.
Такой подход изолирует логику темы от других модулей и позволяет легко её повторно использовать.
7. Вкладки в сайдбаре (tabs.js) – кратко
Файл tabs.js отвечает за переключение между «Все валюты» и «Избранное». Это часть сайдбара, но её реализация проста и не влияет на адаптивность. При клике на кнопку-чипс мы:
- Переносим класс
active на нужную кнопку. - Показываем/скрываем контейнер с валютами (
allCurrenciesContainer). - Показываем/скрываем заглушку «Избранные направления отсутствуют» через класс
visible.
Никакой сложной логики, только переключение видимости. Важно лишь проверить, что все элементы существуют.
8. Остальные модули: swap.js и search-demo.js
Эти файлы решают локальные задачи и не требуют детального разбора в рамках статьи про сайдбар.
swap.js содержит функцию, которая меняет местами значения двух полей (по их id) – это нужно для удобства пользователей. Кнопки обмена есть и в сайдбаре, и в основном калькуляторе.
search-demo.js – временная заглушка, выводящая alert при нажатии на кнопку «Найти». В будущем сюда можно добавить AJAX-запрос.
Их наличие демонстрирует, как можно добавлять новые функции, не затрагивая основную логику сайдбара.
9. Почему такое разделение важно
Вынос стилей в отдельный файл и разбиение JavaScript на модули даёт несколько преимуществ:
- Чистота HTML – разметка остаётся семантичной и легко читаемой.
- Переиспользование – модуль
theme.js можно подключить на любой странице, где нужна тема. - Простота поддержки – чтобы исправить баг с сайдбаром, вы идёте в один файл
sidebar.js. - Параллельная работа – несколько разработчиков могут одновременно править разные модули без конфликтов.
Наш проект не использует сборщики (Webpack, Vite) и ES6-импорты, но это осознанное решение – для небольшого сервиса достаточно простого разделения с помощью defer.
10. Заключение
Мы прошли весь путь от идеи до полностью рабочего адаптивного сайдбара. На широких экранах он всегда на виду, на мобильных – выезжает по требованию. Цветовая тема переключается без перезагрузки. Код разделён на логические части, и каждая строка имеет понятное объяснение.
Этот подход вы можете применить в любом проекте: интернет-магазине, админке, лендинге. Главное – понимать, что адаптивность закладывается в CSS, а JavaScript лишь управляет состояниями.
Теперь у вас есть не просто набор файлов, а глубокое понимание того, как сделать современный, удобный и поддерживаемый интерфейс. Успешной разработки!