Перейти к содержанию

Отключить переходы по пунктам меню на мобильных

Инструкция
Как на мобильных сделать так, чтобы клик по родительскому пункту меню раскрывал подменю, а не уводил на страницу раздела. Современное решение на чистом JS, matchMedia и с сохранением доступности.

5 мин 40 Обновлено 22 июля Сложность: Базовая

TL;DR

  • На мобильных тап по родительскому пункту должен раскрывать подменю, а не уводить на страницу.
  • Мобильный режим определяйте через window.matchMedia, а не по видимости элемента.
  • Отменяйте переход только у пунктов с подменю, обычные ссылки не трогайте.
  • Переключайте aria-expanded, чтобы сохранить доступность.
  • Решение на чистом JS не требует jQuery.

Типичная проблема адаптивного меню: на десктопе подменю раскрывается по наведению, а на телефоне наведения нет. В итоге тап по родительскому пункту сразу уводит на страницу раздела, и до вложенных ссылок пользователь не добирается. Задача – на мобильных перехватить первый тап по пункту с подменю, чтобы он раскрывал список, а не выполнял переход.

Почему старое решение на jQuery устарело

Раньше это делали через $(document).delegate('a', 'click', ...) и проверку видимости бургер-кнопки. Приём рабочий, но у него три слабых места: зависимость от jQuery, определение «мобильности» по видимости элемента вместо реального размера экрана и полное отсутствие доступности – скринридер не понимает, что пункт стал кнопкой-раскрывашкой. Сегодня всё это решается на чистом JavaScript.

Определяем мобильный режим правильно

Вместо проверки :visible используйте window.matchMedia() – он опрашивает ту же контрольную точку, что и CSS, и не зависит от вёрстки:

const mobile = window.matchMedia("(max-width: 992px)");

Перехватываем клик на чистом JS

Навешиваем один обработчик на контейнер меню (делегирование событий) и отменяем переход только для пунктов, у которых есть подменю, и только в мобильном режиме:

const menu = document.querySelector("#main-menu");

menu.addEventListener("click", (e) => {
    const link = e.target.closest("a.has-submenu");
    if (!link) return;

    if (mobile.matches) {
        e.preventDefault();               // отменяем переход
        const item = link.parentElement;
        const expanded = item.classList.toggle("is-open");
        link.setAttribute("aria-expanded", expanded);
    }
});

Обратите внимание: отмену перехода получают только пункты с классом has-submenu. Обычные ссылки без вложенного меню продолжают работать как ссылки – это важно, иначе на мобильных перестанет работать вся навигация.

Доступность: не ломайте семантику

Если пункт на мобильных перестаёт быть ссылкой и становится переключателем, об этом нужно сообщить вспомогательным технологиям:

  • Добавляйте aria-expanded="false" на пункты с подменю и переключайте значение при раскрытии (в коде выше это уже сделано).
  • Оборачивайте меню в <nav aria-label="Основное меню">.
  • Убедитесь, что подменю раскрывается и с клавиатуры: событие click срабатывает и по Enter на ссылке, так что базовый кейс закрыт, но проверьте порядок фокуса.
  • Не прячьте у родителя реальную ссылку насовсем – оставьте пользователю способ попасть на страницу раздела (например, отдельной ссылкой «Перейти в раздел» внутри раскрытого подменю).

Частые ошибки

  • Отключили переход у всех ссылок. Тогда на телефоне не работает вообще ничего. Отменяйте переход только у пунктов с подменю.
  • Определяете мобильность по ширине окна в JS-переменной один раз. При повороте экрана значение устаревает. matchMedia всегда актуален через .matches.
  • Нет визуального признака раскрытия. Пользователь не понимает, что пункт раскрылся. Показывайте стрелку/шеврон и меняйте её по классу is-open.

Тему меню продолжают заметки как вручную добавить подпункт в подменю Битрикса и вывод блоков только на определённых страницах. Если правите ещё и мобильные кнопки, посмотрите как заменить текст кнопки поиска на иконку.

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

Почему нельзя просто отменить переход у всех ссылок меню?

Тогда на мобильных перестанет работать вся навигация – даже пункты без подменю. Отменяйте переход только у родительских пунктов, у которых есть вложенный список.

Чем matchMedia лучше проверки видимости бургер-кнопки?

matchMedia опрашивает ту же брейкпоинт-логику, что и CSS, не зависит от вёрстки и всегда актуален через свойство .matches, в том числе после поворота экрана.

Нужен ли jQuery для этого решения?

Нет. Современный делегированный обработчик на addEventListener с event.target.closest полностью заменяет старый $(document).delegate.

Как не потерять доступность при отключении переходов?

Переключайте атрибут aria-expanded на пункте, оборачивайте меню в nav с aria-label и оставьте пользователю отдельную ссылку на страницу раздела внутри раскрытого подменю.

Как пользователю всё-таки попасть на страницу раздела?

Добавьте в раскрытое подменю отдельную ссылку вида «Перейти в раздел» – так родительский пункт раскрывает список, а переход остаётся доступным.

Материалы по теме

Что почитать дальше по этой теме

Инструкция Как вручную добавить подпункт в подменю Битрикса Как добавить свой пункт в меню 1С-Битрикс без правки инфоблока: статические файлы .menu.php, динамические .menu_ext.php, структура массива aMenuLinks и управление позицией пункта через array_merge. 5 мин 53 23 октября 2018 Инструкция Добавить иконку в кнопку поиска по новостям в Битрикс Как заменить текст на кнопке поиска компонента bitrix:search.form иконкой лупы. Разбираем современный способ – кнопку с inline-SVG и aria-label, старый приём с иконочным шрифтом Font Awesome и почему важно не сломать доступность. 7 мин 44 18 февраля 2017 Инструкция Вывод блоков только на определённых страницах сайта Как в 1С-Битрикс показать баннер, форму или блок только на нужных страницах. Разбираем API $APPLICATION->GetCurPage и GetCurDir, разницу между физическим и SEF-адресом, проверку по разделу и типичные ошибки с ЧПУ. 7 мин 58 24 октября 2018 Инструкция Новый шаблон постраничной навигации в Битрикс Как создать свой шаблон постраничной навигации для компонента «Новости» в 1С-Битрикс, подключить его к списку и результатам поиска через PAGER_TEMPLATE и сделать пагинацию доступной и семантичной. 6 мин 47 18 мая 2017