Отключить переходы по пунктам меню на мобильных
Инструкция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 и оставьте пользователю отдельную ссылку на страницу раздела внутри раскрытого подменю.
Как пользователю всё-таки попасть на страницу раздела?
Добавьте в раскрытое подменю отдельную ссылку вида «Перейти в раздел» – так родительский пункт раскрывает список, а переход остаётся доступным.
Материалы по теме
Что почитать дальше по этой теме