- Идеально для Top Bar меню
- jQuery FlexMenu (классика)
- 📦 Исходники jQuery версии:
- Особенности jQuery версии
- Пример использования jQuery версии
- Параметры jQuery версии
- Современная версия на Vanilla JS
- Сравнение версий
- HTML структура (Vanilla JS)
- CSS стили (основные)
- JavaScript код (полный)
- Как работает алгоритм
- Алгоритм calculateVisibleItems():
- Debounce для производительности
- Инициализация и использование
- Настройка под свои нужды
- Стилизация
- Основные элементы:
- Пример стилизации:
- Тестирование
- Тестирование на разных устройствах:
- Когда использовать какую версию?
- Используйте jQuery версию если:
- Используйте Vanilla JS версию если:
- Заключение
FlexMenu — это умное решение для адаптивного меню, которое автоматически определяет сколько пунктов помещается в строку и прячет остальные в выпадающий список «Еще».
Почему FlexMenu особенное?
В отличие от обычных адаптивных меню с медиа-запросами (например «на экране < 768px показывай гамбургер»), FlexMenu работает динамически:
❌ Обычное меню: фиксированные брейкпоинты (768px, 1024px…).
✅ FlexMenu: умный расчет в реальном времени — показывает столько пунктов, сколько помещается прямо сейчас.
Идеально для Top Bar меню
FlexMenu особенно хорошо подходит для верхнего меню (top bar), где:
- Много пунктов меню (8-15 штук);
- Важно показать максимум пунктов на любом экране;
- Не хочется прятать всё меню в гамбургер на средних экранах;
- Нужна плавная адаптация под разные разрешения.
Примеры использования:
- Информационные порталы с множеством разделов;
- Интернет-магазины с большим каталогом;
- Корпоративные сайты с многоуровневой структурой;
- Новостные сайты с категориями/
jQuery FlexMenu (классика)
Оригинальное решение было создано как jQuery плагин. Я нашел его на GitHub и немного переработал.
📦 Исходники jQuery версии:
- GitHub: https://github.com/352Media/flexMenu (оригинальный репозиторий).
- Демо 1: Простое меню.
- Демо 2: С выпадающими подпунктами.
- Мои исходники: немного переработаны и оптимизированы.
Особенности jQuery версии
- Стабильность: проверена временем (с 2014 года).
- Простота: легкая инициализация через
$('#menu').flexMenu(). - Совместимость: работает со старыми браузерами (IE9+).
- Документация: хорошо описана на GitHub.
- Недостаток: требует jQuery (~95 KB дополнительно).
Пример использования jQuery версии
<!-- Подключаем jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Подключаем плагин -->
<script src="jquery.flexMenu.js"></script>
<!-- HTML структура -->
<ul id="menu">
<li><a href="#">Главная</a></li>
<li><a href="#">О компании</a></li>
<li><a href="#">Услуги</a></li>
<!-- ... остальные пункты ... -->
</ul>
<!-- Инициализация -->
<script>
$(document).ready(function(){
$('#menu').flexMenu({
threshold: 100,
cutoff: 2,
linkText: 'Еще',
linkTitle: 'Показать все пункты'
});
});
</script>
Параметры jQuery версии
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
threshold |
Number | 0 | Отступ справа для кнопки «Еще» (px) |
cutoff |
Number | 2 | Минимум видимых пунктов перед скрытием |
linkText |
String | «More» | Текст кнопки |
linkTitle |
String | «View More» | Title атрибут кнопки |
showOnHover |
Boolean | true | Открывать dropdown при наведении |
Современная версия на Vanilla JS
Я создал современную версию FlexMenu на чистом JavaScript без jQuery и каких-либо зависимостей. Она работает быстрее, легче и использует современные возможности ES6+.
Преимущества Vanilla JS версии:
- 0 зависимостей: не нужен jQuery (экономия ~95 KB).
- Современный код: ES6+, arrow functions, template literals.
- Быстрее: нативные методы работают быстрее jQuery.
- Легче: всего ~2 KB кода против 95 KB (jQuery + плагин).
- Оптимизация: debounce для resize, умный пересчет.
Сравнение версий
| Параметр | jQuery FlexMenu | Vanilla JS версия |
|---|---|---|
| Размер | ~95 KB (jQuery 85KB + плагин 10KB) | ~2 KB |
| Зависимости | jQuery | Нет (0 KB) |
| Скорость загрузки | Медленнее (парсинг jQuery) | Быстрее в 5-10 раз |
| Производительность | Хорошо | Отлично (нативные методы) |
| Современность | ES5 (2014 год) | ES6+ (2025 год) |
| Поддержка браузеров | IE9+ (с jQuery) | Современные браузеры (Chrome, Firefox, Safari, Edge) |
| Debounce на resize | ❌ Нет | ✅ Есть |
| Счетчик скрытых пунктов | ❌ Нет | ✅ Есть |
| Анимации | Базовые | CSS animations |
HTML структура (Vanilla JS)
Структура немного отличается от jQuery версии — мы явно указываем контейнер для кнопки «Еще»:
<nav class="flex-menu" id="flexMenu">
<ul class="flex-menu-list" id="flexMenuList">
<li class="flex-menu-item active"><a href="#">Главная</a></li>
<li class="flex-menu-item"><a href="#">О компании</a></li>
<li class="flex-menu-item"><a href="#">Услуги</a></li>
<li class="flex-menu-item"><a href="#">Портфолио</a></li>
<li class="flex-menu-item"><a href="#">Команда</a></li>
<!-- ... больше пунктов ... -->
</ul>
<!-- Кнопка "Еще" с dropdown -->
<div class="flex-menu-more" id="flexMenuMore">
<button class="flex-menu-more-toggle" type="button">
<span>Еще</span>
<span class="flex-menu-more-count" id="moreCount">0</span>
<span class="flex-menu-more-arrow">▾</span>
</button>
<div class="flex-menu-more-dropdown">
<ul id="moreDropdown"></ul>
</div>
</div>
</nav>
CSS стили (основные)
Ключевые стили для работы FlexMenu:
/* Flex контейнер */
.flex-menu {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
}
/* Список пунктов меню */
.flex-menu-list {
display: flex;
align-items: center;
list-style: none;
gap: 5px;
flex: 1;
overflow: hidden;
}
.flex-menu-item {
white-space: nowrap;
flex-shrink: 0;
}
/* Скрытые пункты */
.flex-menu-item.hidden {
display: none !important;
}
/* Кнопка "Еще" */
.flex-menu-more {
position: relative;
display: none;
margin-left: 10px;
flex-shrink: 0;
}
.flex-menu-more.active {
display: block;
}
/* Dropdown */
.flex-menu-more-dropdown {
position: absolute;
top: calc(100% + 8px);
right: 0;
background: white;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.15);
min-width: 200px;
display: none;
}
.flex-menu-more.open .flex-menu-more-dropdown {
display: block;
}
JavaScript код (полный)
Весь функционал в одной самовызывающейся функции:
(function() {
'use strict';
// Элементы
const menu = document.getElementById('flexMenu');
const menuList = document.getElementById('flexMenuList');
const menuItems = Array.from(menuList.querySelectorAll('.flex-menu-item'));
const moreContainer = document.getElementById('flexMenuMore');
const moreToggle = moreContainer.querySelector('.flex-menu-more-toggle');
const moreCount = document.getElementById('moreCount');
const moreDropdown = document.getElementById('moreDropdown');
let isCalculating = false;
let resizeTimer = null;
/**
* Вычисляем сколько пунктов помещается
*/
function calculateVisibleItems() {
if (isCalculating) return;
isCalculating = true;
// Показываем все пункты
menuItems.forEach(item => item.classList.remove('hidden'));
moreContainer.classList.remove('active');
// Получаем ширины
const menuWidth = menu.offsetWidth;
const moreButtonWidth = 120;
const padding = 40;
let availableWidth = menuWidth - moreButtonWidth - padding;
let currentWidth = 0;
let visibleCount = 0;
// Считаем сколько пунктов помещается
for (let i = 0; i < menuItems.length; i++) {
const itemWidth = menuItems[i].offsetWidth + 5;
if (currentWidth + itemWidth <= availableWidth) {
currentWidth += itemWidth;
visibleCount++;
} else {
break;
}
}
// Если не все пункты помещаются
if (visibleCount < menuItems.length) {
moreContainer.classList.add('active');
// Прячем лишние пункты
const hiddenItems = [];
for (let i = visibleCount; i < menuItems.length; i++) {
menuItems[i].classList.add('hidden');
hiddenItems.push(menuItems[i]);
}
// Обновляем dropdown
updateMoreDropdown(hiddenItems);
} else {
moreContainer.classList.remove('active');
moreDropdown.innerHTML = '';
}
isCalculating = false;
}
/**
* Обновляем dropdown "Еще"
*/
function updateMoreDropdown(hiddenItems) {
moreDropdown.innerHTML = '';
moreCount.textContent = hiddenItems.length;
hiddenItems.forEach(item => {
const li = document.createElement('li');
const link = item.querySelector('a').cloneNode(true);
if (item.classList.contains('active')) {
li.classList.add('active');
}
li.appendChild(link);
moreDropdown.appendChild(li);
});
}
/**
* Toggle dropdown
*/
function toggleDropdown(e) {
e.preventDefault();
e.stopPropagation();
moreContainer.classList.toggle('open');
}
/**
* Закрыть dropdown при клике вне
*/
function closeDropdown(e) {
if (!moreContainer.contains(e.target)) {
moreContainer.classList.remove('open');
}
}
/**
* Debounced resize handler
*/
function handleResize() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(calculateVisibleItems, 150);
}
// Инициализация
function init() {
moreToggle.addEventListener('click', toggleDropdown);
document.addEventListener('click', closeDropdown);
window.addEventListener('resize', handleResize);
// Первоначальный расчет
setTimeout(calculateVisibleItems, 100);
window.addEventListener('load', calculateVisibleItems);
console.log('✅ FlexMenu инициализирован');
}
// Запуск
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
Как работает алгоритм
Разберем пошагово что происходит при расчете видимых пунктов:
Алгоритм calculateVisibleItems():
- Сброс: Показываем все пункты меню, убираем класс
.hidden - Измерение: Получаем ширину контейнера меню
- Резервирование: Вычитаем место под кнопку «Еще» (~120px) и отступы (40px)
- Подсчет: В цикле проходим по всем пунктам:
- Измеряем ширину текущего пункта
- Проверяем поместится ли он в доступную ширину
- Если да — увеличиваем счетчик видимых
- Если нет — прерываем цикл
- Скрытие: Все пункты после
visibleCountполучают класс.hidden - Dropdown: Скрытые пункты добавляются в выпадающий список
- Счетчик: Обновляем бейдж с количеством скрытых пунктов
Debounce для производительности
При изменении размера окна событие resize срабатывает сотни раз в секунду. Чтобы не нагружать браузер, используется техника debounce:
function handleResize() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(calculateVisibleItems, 150);
}
window.addEventListener('resize', handleResize);
Как работает debounce:
- При каждом событии
resizeмы отменяем предыдущий таймер - Ставим новый таймер на 150 мс
- Расчет происходит только когда пользователь прекратил изменять размер окна
- Экономия: вместо 500 вызовов — всего 1!
Инициализация и использование
Vanilla JS версия инициализируется автоматически при загрузке страницы. Просто добавьте HTML, CSS и JS — всё заработает!
Важные моменты:
- ID элементов должны совпадать:
flexMenu,flexMenuList,flexMenuMore.- Структура HTML должна быть точно как в примере.
- CSS классы обязательны:
.flex-menu-item,.hidden,.active.- JavaScript должен быть в конце
<body>или сdefer.
Настройка под свои нужды
Вы можете легко изменить поведение FlexMenu изменив параметры в коде:
// В функции calculateVisibleItems() найдите:
const moreButtonWidth = 120; // Ширина кнопки "Еще"
const padding = 40; // Общие отступы
// В handleResize() найдите:
resizeTimer = setTimeout(calculateVisibleItems, 150); // Задержка debounce
// Измените текст кнопки в HTML:
<span>Еще</span> // Замените на "More", "Больше" и т.д.
Стилизация
FlexMenu легко стилизуется под любой дизайн. Вот базовые классы которые нужно оформить:
Основные элементы:
.flex-menu— контейнер всего меню..flex-menu-list— список пунктов меню..flex-menu-item— отдельный пункт меню..flex-menu-item.active— активный пункт..flex-menu-more— контейнер кнопки «Еще»..flex-menu-more-toggle— сама кнопка «Еще»..flex-menu-more-dropdown— выпадающий список.
Пример стилизации:
/* Темная тема */
.flex-menu {
background: #2d3436;
}
.flex-menu-item a {
color: #dfe6e9;
}
.flex-menu-item a:hover {
background: #636e72;
}
/* Градиентная кнопка "Еще" */
.flex-menu-more-toggle {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
}
/* Анимация появления dropdown */
.flex-menu-more-dropdown {
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Тестирование
Чтобы протестировать FlexMenu:
- Откройте демо-файл в браузере на широком экране (> 1200px)
- Все пункты меню должны быть видимы, кнопки «Еще» нет
- Медленно уменьшайте ширину окна браузера
- Наблюдайте как пункты по одному исчезают справа
- Появляется кнопка «Еще» с счетчиком
- Кликните на «Еще» — выпадет список со скрытыми пунктами
- Увеличьте окно обратно — пункты вернутся в основное меню
Тестирование на разных устройствах:
Откройте DevTools (F12) → нажмите Ctrl+Shift+M → выберите разные устройства:
- Desktop 1920px: Все пункты видны
- Desktop 1440px: 1-2 пункта в «Еще»
- Desktop 1024px: 3-4 пункта в «Еще»
- Tablet 768px: 6-7 пунктов в «Еще»
- Mobile 375px: Большинство в «Еще»
Когда использовать какую версию?
Используйте jQuery версию если:
- На сайте уже используется jQuery для других задач.
- Нужна поддержка старых браузеров (IE9-11).
- Проект создан давно и переписывать нет смысла.
- Команда знакома только с jQuery.
Используйте Vanilla JS версию если:
- Создаете новый проект с нуля.
- Важна производительность и скорость загрузки.
- Не хотите зависимостей (jQuery).
- Целевая аудитория — современные браузеры.
- Хотите легкий и чистый код.
Заключение
FlexMenu — это умное решение для адаптивных меню, особенно для top bar с большим количеством пунктов. В отличие от классических решений с медиа-запросами, FlexMenu динамически определяет сколько пунктов помещается и автоматически прячет лишние.
Две версии на выбор:
- jQuery версия — классика, стабильная, с поддержкой старых браузеров
- Vanilla JS версия — современная, быстрая, легкая, без зависимостей
Выбирайте версию в зависимости от требований проекта и наслаждайтесь умным адаптивным меню!









