FlexMenu — умное адаптивное меню

FlexMenu — умное адаптивное меню Верстка
Автоматически прячет пункты в dropdown "Еще" если не помещаются. jQuery и современная Vanilla JS версии.

FlexMenu — это умное решение для адаптивного меню, которое автоматически определяет сколько пунктов помещается в строку и прячет остальные в выпадающий список «Еще».

Почему FlexMenu особенное?

В отличие от обычных адаптивных меню с медиа-запросами (например «на экране < 768px показывай гамбургер»), FlexMenu работает динамически:

Обычное меню: фиксированные брейкпоинты (768px, 1024px…).

FlexMenu: умный расчет в реальном времени — показывает столько пунктов, сколько помещается прямо сейчас.

Demo jQuery версии
Demo Vanilla JS версии

Идеально для 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():

  1. Сброс: Показываем все пункты меню, убираем класс .hidden
  2. Измерение: Получаем ширину контейнера меню
  3. Резервирование: Вычитаем место под кнопку «Еще» (~120px) и отступы (40px)
  4. Подсчет: В цикле проходим по всем пунктам:
    • Измеряем ширину текущего пункта
    • Проверяем поместится ли он в доступную ширину
    • Если да — увеличиваем счетчик видимых
    • Если нет — прерываем цикл
  5. Скрытие: Все пункты после visibleCount получают класс .hidden
  6. Dropdown: Скрытые пункты добавляются в выпадающий список
  7. Счетчик: Обновляем бейдж с количеством скрытых пунктов

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:

  1. Откройте демо-файл в браузере на широком экране (> 1200px)
  2. Все пункты меню должны быть видимы, кнопки «Еще» нет
  3. Медленно уменьшайте ширину окна браузера
  4. Наблюдайте как пункты по одному исчезают справа
  5. Появляется кнопка «Еще» с счетчиком
  6. Кликните на «Еще» — выпадет список со скрытыми пунктами
  7. Увеличьте окно обратно — пункты вернутся в основное меню

Тестирование на разных устройствах:

Откройте 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 версия — современная, быстрая, легкая, без зависимостей

Выбирайте версию в зависимости от требований проекта и наслаждайтесь умным адаптивным меню!

Смотреть / Скачать Vanilla JS версию
Поделиться с друзьями
Алексей

Веб-дизайнер и SEO оптимизатор. Занимаюсь созданием сайтов с 2010 года и их продвижение с 2012 года!

Оцените автора
( Пока оценок нет )
Web-Revenue.ru
Добавить комментарий