Текущий раздел Битрикс определяю почти в каждом проекте, где меню не однослойное: интернет-магазин с категориями, корпоративный сайт с вложенными подразделами, портал с личным кабинетом. Компонент 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() - метод возвращает путь директории без имени файла независимо от того, есть на странице инфоблочный компонент или нет. Сравнение делаю по префиксу пути, а не по точному совпадению, чтобы вложенные статичные страницы тоже подсвечивали родительский раздел меню.