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

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка мегаменю на 1С-Бітрікс
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • 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
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

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

Стандартний компонент bitrix:menu виводить плаский список посилань. Великий інтернет-магазин вимагає іншого: багатоколонкова навігація по розділах, швидкий доступ до другого та третього рівня ієрархії без зайвих кліків, підтримка тисяч категорій без деградації продуктивності. Це мегаменю — окремий компонент поверх інфоблока розділів.

Джерело даних: дерево розділів

Структура розділів Bitrix зберігається в 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 Bitrix
        $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 дні