1С Битрикс · 7 мин чтения

Как определить текущий раздел Битрикс и подсветить меню

Текущий раздел Битрикс определяю почти в каждом проекте, где меню не однослойное: интернет-магазин с категориями, корпоративный сайт с вложенными подразделами, портал с личным кабинетом. Компонент bitrix:menu решает задачу подсветки активного пункта из коробки процентов на семьдесят, а остальное собираю руками - кастомные мегаменю, каталожные разделы с иерархией в три уровня, кэш, который начинает подсвечивать не тот пункт после первого посетителя. Разберу, какими переменными и методами Битрикс сам отслеживает текущее расположение на сайте, и как использовать эти данные для подсветки меню без переписывания компонента с нуля.

Зачем вообще подсвечивать текущий раздел в меню

Активный пункт меню - это не декоративная деталь. На сайте с пятнадцатью-двадцатью категориями каталога пользователь ориентируется по подсветке, а не по хлебным крошкам, которые часто вынесены мелким шрифтом под шапкой. Если раздел не подсвечен, увеличивается число возвратов на главную и переходов через поиск вместо навигации по каталогу - это видно в Яндекс.Метрике по карте кликов на любом проекте, где меню отработано плохо.

Вторая причина практическая: без корректного определения раздела ломается логика соседних блоков - хлебных крошек, related-категорий в сайдбаре, SEO-текста под конкретный раздел, скриптов аналитики, которые передают в события категорию просматриваемой страницы. Все эти блоки обычно тянут раздел из тех же переменных, что и меню, поэтому разбираться в этом один раз стоит сразу для всех задач, а не только для одной перекраски пункта в меню.

Как Битрикс хранит информацию о текущем разделе

Базовый источник - глобальный объект $APPLICATION и его методы, которые не зависят от того, есть на странице инфоблоки или нет.

global $APPLICATION;

echo $APPLICATION->GetCurDir();
// /catalog/noutbuki/

echo $APPLICATION->GetCurPage();
// /catalog/noutbuki/index.php

echo $APPLICATION->GetCurPageParam("SECTION_ID=5", array("SECTION_ID"));
// добавит или заменит GET-параметр в текущем URL

GetCurDir() возвращает путь без имени файла - это то, что чаще всего сравнивают со ссылкой пункта меню. GetCurPage() отдаёт полный путь вместе с файлом, а в режиме без ЧПУ ещё и с GET-параметрами - удобно для точного сравнения адреса, но не годится для меню, где ссылки указывают на директорию раздела.

Если на странице стоит компонент инфоблока (каталог, новости, любой список с разделами), текущий раздел приходит через $arResult["VARIABLES"]["SECTION_ID"] в шаблоне компонента, либо через переменную $_REQUEST["SECTION_ID"] в режиме без ЧПУ. При включённых SEF-правилах ID раздела в URL может вообще не фигурировать - тогда Битрикс достаёт его сам по правилу ЧПУ инфоблока и подставляет в те же переменные результата компонента. Проверяю это через CIBlockSection::GetByID($ID)->GetNext(), если нужно получить не только ID, но и код, название, путь раздела.

Для разделов, которые не завязаны на инфоблок, а собраны вручную через директории (типично для статичных страниц вроде «О компании», «Доставка», «Гарантия»), единственный источник - как раз GetCurDir(), и сравнивать нужно по префиксу пути, а не по точному совпадению.

Подсветка пункта меню через файл .top.menu.php

Стандартный способ управления меню в Битрикс - файлы вида .top.menu.php, .left.menu.php в нужных директориях. Формат - плоский массив пунктов, где Битрикс сам сравнивает ссылку каждого пункта с текущим адресом и выставляет класс выбранного элемента в шаблоне компонента bitrix:menu.

$aMenuLinks = Array(
    Array(
        "Каталог",
        "/catalog/",
        Array(),
        Array(),
        ""
    ),
    Array(
        "Ноутбуки",
        "/catalog/noutbuki/",
        Array(),
        Array(),
        ""
    ),
    Array(
        "Доставка",
        "/delivery/",
        Array(),
        Array(),
        ""
    ),
);
return $aMenuLinks;

Алгоритм подсветки в компоненте меню такой: если ссылка пункта совпадает с текущим адресом посимвольно - пункт активен; если ссылка заканчивается на «/» и текущий путь начинается с неё - пункт тоже считается активным (это и есть логика подсветки родителя для вложенных разделов). Из-за этого при просмотре страницы /catalog/noutbuki/lenovo-thinkpad/ одновременно подсвечиваются и «Каталог», и «Ноутбуки» - оба пункта являются префиксом текущего пути.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Проблема начинается, когда меню не файловое, а собрано вручную из массива категорий инфоблока или из настроек в админке - тогда автоматической подсветки нет, и сравнение приходится писать самому.

Подсветка мегаменю и разделов каталога через инфоблоки

В интернет-магазине меню обычно строится не из .top.menu.php, а из дерева разделов каталога - трёх-четырёх уровней вложенности, с картинками и счётчиками товаров в мегаменю. Здесь сравнение по URL ненадёжно, потому что при включённых SEF-правилах адрес раздела может не совпадать буквально с адресом ссылки в меню (например, при ЧПУ с кодом раздела вместо ID). Надёжнее сравнивать по ID раздела.

$curSectionId = (int)$arResult["VARIABLES"]["SECTION_ID"];
$activeIds = array($curSectionId);

if ($curSectionId > 0) {
    $rsChain = CIBlockSection::GetNavChain(false, array("ID" => $curSectionId));
    while ($arChain = $rsChain->GetNext()) {
        $activeIds[] = (int)$arChain["ID"];
    }
}

foreach ($arMenuSections as $item) {
    $class = in_array((int)$item["ID"], $activeIds) ? "menu-item menu-item--active" : "menu-item";
    echo '<a class="' . $class . '" href="' . $item["SECTION_PAGE_URL"] . '">' . $item["NAME"] . '</a>';
}

GetNavChain отдаёт цепочку разделов от текущего до корня - за счёт этого при просмотре подкатегории третьего уровня подсвечивается и она сама, и родительский пункт верхнего уровня в мегаменю, даже если внешне это выглядит как один клик, а не переход по вложенным ссылкам. Такую логику я обычно выношу в отдельный include-файл, чтобы не дублировать её между шаблоном шапки и шаблоном мобильного меню - это частая доработка, когда типовое меню Битрикс не тянет мегаменю с картинками разделов, и проще собрать кастомный компонент под задачу, чем выкручивать типовой bitrix:menu через переопределение шаблона.

Частые ошибки при определении текущего раздела

Ошибка Почему возникает Как исправляю
Меню подсвечивает не тот пункт после первого визита Компонент bitrix:menu закэширован (CACHE_TYPE=“A”), а подсветка выбранного пункта не пересчитывается для каждого пользователя отдельно Добавляю параметр CACHE_SELECTED_ITEMS=“Y” в вызов компонента - он заставляет пересчитывать выбранный пункт вне кэшированного блока
SECTION_ID пустой при включённом ЧПУ Правило URL инфоблока не прописано в urlrewrite.php или указывает не на тот компонент Проверяю правила через административный раздел «Настройки продукта - Правила ЧПУ» и сверяю с шаблоном компонента
Родительский пункт не подсвечивается на подкатегории Сравнение идёт только по точному ID раздела, без построения цепочки родителей Использую CIBlockSection::GetNavChain вместо прямого сравнения ID
Активный класс исчезает на мобильной версии меню Мобильное меню отрисовано отдельным шаблоном или JS-компонентом, который не получает те же переменные результата Вынес логику подсветки в общий include, который подключают оба шаблона

Определение раздела на статичных страницах без инфоблоков

Если раздел - это просто директория со статичными страницами (типично для «О компании», «Вакансии», «Контакты»), инфоблочных переменных нет вообще, и остаётся сравнение по GetCurDir().

global $APPLICATION;
$curDir = $APPLICATION->GetCurDir();

function isSectionActive($link, $curDir)
{
    if ($link === $curDir) {
        return true;
    }
    return (substr($link, -1) === "/" && strpos($curDir, $link) === 0);
}

Это ровно та логика, которую под капотом использует стандартный компонент меню для файловых пунктов - её удобно переиспользовать, когда часть меню строится из файла .top.menu.php, а часть - вручную из массива в шаблоне компании. Смешанные меню (часть пунктов статичные, часть - из инфоблока) я встречаю примерно в трети проектов на Битрикс, где сайт рос годами и структура каталога добавлялась поверх уже существующего корпоративного раздела.

Таблица способов определения раздела

Способ Что возвращает Когда использую
$APPLICATION->GetCurDir() Путь к директории без имени файла Статичные страницы и файловое меню .top.menu.php
$APPLICATION->GetCurPage() Полный путь с файлом, в режиме без ЧПУ - с GET-параметрами Точное сравнение адреса при построении ссылок с параметрами
$arResult[“VARIABLES”][“SECTION_ID”] ID активного раздела инфоблока из компонента каталога Мегаменю и хлебные крошки в интернет-магазине
CIBlockSection::GetNavChain() Цепочка родительских разделов до корня инфоблока Подсветка родительского пункта при просмотре вложенной подкатегории
CACHE_SELECTED_ITEMS=“Y” в bitrix:menu Пересчёт подсветки вне кэшированного блока меню Любое меню с включённым кэшированием CACHE_TYPE=“A”

Отдельно отмечу: если сайт собран не на типовом каталоге, а на кастомной структуре с несколькими инфоблоками под разные типы разделов (услуги, статьи, товары в одном меню), готового рецепта из документации Битрикс уже не хватает - логику подсветки собираю индивидуально под структуру данных проекта.

Частые вопросы

Как узнать SECTION_ID текущего раздела инфоблока в компоненте каталога?

Он приходит в переменной $arResult[“VARIABLES”][“SECTION_ID”] шаблона компонента bitrix:catalog или bitrix:news.list при просмотре раздела. Если компонент вызван в режиме без ЧПУ, тот же ID можно получить напрямую из $_REQUEST[“SECTION_ID”], но полагаться на GET-параметр не стоит - при включённых SEF-правилах его в адресе может не быть.

Почему меню на Битрикс не подсвечивает активный пункт после включения кэша?

Потому что компонент bitrix:menu кэширует HTML-результат целиком, включая класс выбранного пункта, и при следующем посетителе отдаёт из кэша ту подсветку, что была у первого. Решение - параметр CACHE_SELECTED_ITEMS=“Y” в вызове компонента, он хранит в кэше отдельно данные пунктов и отдельно пересчитывает активный класс на каждом запросе.

Как подсветить родительский пункт меню, если открыта дочерняя подкатегория?

Сравнением по точному ID раздела это не решается - нужна цепочка родителей через CIBlockSection::GetNavChain(false, array(“ID” => $curSectionId)). Функция отдаёт все разделы от текущего до корня инфоблока, и подсветку выставляю всем ID из этой цепочки, а не только текущему.

Можно ли определить текущий раздел на статичной HTML-странице без инфоблоков?

Да, через $APPLICATION->GetCurDir() - метод возвращает путь директории без имени файла независимо от того, есть на странице инфоблочный компонент или нет. Сравнение делаю по префиксу пути, а не по точному совпадению, чтобы вложенные статичные страницы тоже подсвечивали родительский раздел меню.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно