Разработка мегаменю на 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Разработка мегаменю на 1С-Битрикс
Средняя
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Разработка на базе Битрикс, Битрикс24, 1С для компании Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    745
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Разработка мегаменю на 1С-Битрикс

Стандартный компонент bitrix:menu выводит плоский список ссылок. Крупный интернет-магазин требует другого: многоколоночная навигация по разделам, быстрый доступ ко второму и третьему уровню иерархии без лишних кликов, поддержка тысяч категорий без деградации производительности. Это мегаменю — отдельный компонент поверх инфоблока разделов.

Источник данных: дерево разделов

Структура разделов Битрикс хранится в b_iblock_section. Для мегаменю нужны два-три уровня вложенности. Стандартный метод CIBlockSection::GetList с параметром INCLUDE_SUBSECTIONS = 'N' не даёт дерево — только плоский список. Строим дерево вручную:

// /local/lib/Menu/MegaMenuBuilder.php
namespace Local\Menu;

class MegaMenuBuilder
{
    public function build(int $iblockId, int $depth = 3): array
    {
        $cache = new \CPHPCache();
        $cacheId = 'megamenu_' . $iblockId . '_' . LANGUAGE_ID;

        if ($cache->InitCache(3600, $cacheId, '/megamenu/')) {
            return $cache->GetVars()['menu'];
        }

        $raw = $this->fetchSections($iblockId, $depth);
        $tree = $this->buildTree($raw);

        $cache->StartDataCache();
        $cache->EndDataCache(['menu' => $tree]);

        return $tree;
    }

    private function fetchSections(int $iblockId, int $depth): array
    {
        $result = [];
        $res = \CIBlockSection::GetList(
            ['LEFT_MARGIN' => 'ASC'],
            [
                'IBLOCK_ID' => $iblockId,
                'ACTIVE'    => 'Y',
                'DEPTH_LEVEL' => [$depth], // до нужного уровня
                'GLOBAL_ACTIVE' => 'Y',
            ],
            false,
            ['ID', 'NAME', 'CODE', 'DEPTH_LEVEL', 'IBLOCK_SECTION_ID',
             'SECTION_PAGE_URL', 'PICTURE', 'UF_MENU_ICON', 'LEFT_MARGIN', 'RIGHT_MARGIN']
        );

        while ($section = $res->GetNext()) {
            $result[$section['ID']] = $section;
        }

        return $result;
    }

    private function buildTree(array $flat, int $parentId = 0): array
    {
        $tree = [];
        foreach ($flat as $section) {
            if ((int)$section['IBLOCK_SECTION_ID'] === $parentId) {
                $section['children'] = $this->buildTree($flat, (int)$section['ID']);
                $tree[] = $section;
            }
        }
        return $tree;
    }
}

Компонент мегаменю

Компонент регистрируется в /local/components/local/megamenu/. Шаблон принимает дерево разделов и рендерит HTML:

// /local/components/local/megamenu/class.php
class LocalMegaMenuComponent extends \CBitrixComponent
{
    public function onPrepareComponentParams($params): array
    {
        $params['IBLOCK_ID'] = (int)($params['IBLOCK_ID'] ?? CATALOG_IBLOCK_ID);
        $params['DEPTH']     = (int)($params['DEPTH'] ?? 3);
        return $params;
    }

    public function executeComponent(): void
    {
        $builder = new \Local\Menu\MegaMenuBuilder();
        $this->arResult['MENU'] = $builder->build(
            $this->arParams['IBLOCK_ID'],
            $this->arParams['DEPTH']
        );

        $this->setFrameMode(false); // отключаем edit mode Битрикс
        $this->includeComponentTemplate();
    }
}

Шаблон мегаменю

// /local/components/local/megamenu/templates/.default/template.php
/** @var array $arResult */
?>
<nav class="megamenu" aria-label="Навигация по каталогу">
    <ul class="megamenu__list">
        <?php foreach ($arResult['MENU'] as $category): ?>
        <li class="megamenu__item" data-id="<?= $category['ID'] ?>">
            <a href="<?= htmlspecialchars($category['SECTION_PAGE_URL']) ?>"
               class="megamenu__link">
                <?php if ($category['UF_MENU_ICON']): ?>
                    <img src="<?= \CFile::GetPath($category['UF_MENU_ICON']) ?>"
                         alt="" class="megamenu__icon" loading="lazy">
                <?php endif ?>
                <span><?= htmlspecialchars($category['NAME']) ?></span>
            </a>

            <?php if (!empty($category['children'])): ?>
            <div class="megamenu__dropdown">
                <div class="megamenu__columns">
                    <?php foreach (array_chunk($category['children'], 8) as $col): ?>
                    <div class="megamenu__col">
                        <?php foreach ($col as $sub): ?>
                        <a href="<?= htmlspecialchars($sub['SECTION_PAGE_URL']) ?>"
                           class="megamenu__sublink">
                            <?= htmlspecialchars($sub['NAME']) ?>
                        </a>
                        <?php if (!empty($sub['children'])): ?>
                        <ul class="megamenu__tertiary">
                            <?php foreach (array_slice($sub['children'], 0, 5) as $third): ?>
                            <li>
                                <a href="<?= htmlspecialchars($third['SECTION_PAGE_URL']) ?>">
                                    <?= htmlspecialchars($third['NAME']) ?>
                                </a>
                            </li>
                            <?php endforeach ?>
                        </ul>
                        <?php endif ?>
                        <?php endforeach ?>
                    </div>
                    <?php endforeach ?>
                </div>
            </div>
            <?php endif ?>
        </li>
        <?php endforeach ?>
    </ul>
</nav>

CSS: открытие без JavaScript

Базовая работа мегаменю через CSS :hover — жест доброй воли к пользователям с медленным JS:

.megamenu__dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    width: 100%;
    background: #fff;
    border-top: 2px solid var(--color-primary);
    box-shadow: 0 8px 24px rgba(0,0,0,.12);
    display: none;
    z-index: 1000;
}

.megamenu__item:hover .megamenu__dropdown,
.megamenu__item:focus-within .megamenu__dropdown {
    display: block;
}

.megamenu__columns {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 1.5rem;
    padding: 1.5rem;
    max-width: 1200px;
    margin: 0 auto;
}

JavaScript: задержка закрытия и доступность

// Задержка 200мс на закрытие — чтобы не мигало при перемещении мыши
document.querySelectorAll('.megamenu__item').forEach(item => {
    let timer;
    item.addEventListener('mouseenter', () => {
        clearTimeout(timer);
        item.querySelector('.megamenu__dropdown')?.style.setProperty('display', 'block');
    });
    item.addEventListener('mouseleave', () => {
        timer = setTimeout(() => {
            item.querySelector('.megamenu__dropdown')?.style.setProperty('display', 'none');
        }, 200);
    });
});

// Клавиатурная навигация
document.querySelectorAll('.megamenu__link').forEach(link => {
    link.addEventListener('keydown', e => {
        if (e.key === 'Enter' || e.key === ' ') {
            const dropdown = link.nextElementSibling;
            if (dropdown) {
                e.preventDefault();
                dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
            }
        }
    });
});

Кеширование и инвалидация

Дерево разделов кешируется на 3600 секунд. При изменении любого раздела каталога — обработчик события OnAfterIBlockSectionUpdate сбрасывает кеш:

AddEventHandler('iblock', 'OnAfterIBlockSectionUpdate', function($fields) {
    $cache = new \CPHPCache();
    $cache->CleanDir('/megamenu/');
});

Производительность

Дерево разделов строится одним запросом к b_iblock_section, результат закеширован. На странице мегаменю не генерирует дополнительных SQL-запросов. При 500 разделах в трёх уровнях вложенности запрос занимает 3-8 мс, построение дерева в PHP — ещё 2-5 мс. Полный render закешированного мегаменю — менее 1 мс.

Сроки реализации

Конфигурация Срок
Базовое мегаменю (2 уровня, hover) 3–4 дня
С третьим уровнем, кешированием, инвалидацией 5–7 дней
Адаптивная версия с мобильным drawer +3–4 дня
С изображениями, баннерами, акциями +2–3 дня