Клик вне элемента на JavaScript: как закрыть меню
← Блог
Frontend · 03.10.2026 · 4 мин чтения

Клик вне элемента на JavaScript: как закрыть меню

Чтобы закрыть меню по клику вне элемента на JavaScript, проверь, входит ли цель события в обёртку меню. Метод contains() учитывает и сам элемент, и его дочерние узлы.

Ниже — пример для выпадающего меню: нажатие снаружи, клавиша Escape и уход фокуса. Затем разберём вариант с HTML-атрибутом popover, где открытие и закрытие выполняет браузер.

1. Клик вне меню: contains() и pointerdown

Добавь HTML меню, затем подключи JavaScript после разметки или с атрибутом defer. Пример рассчитан на одну обёртку .dropdown: она включает и кнопку, и раскрывающуюся панель.

<div class="dropdown">
  <button class="dropdown__toggle" type="button" aria-expanded="false" aria-controls="dropdown-menu">
    Меню
  </button>
  <div class="dropdown__menu" id="dropdown-menu" hidden>
    <a href="/blog/">Блог</a>
    <a href="/contacts/">Контакты</a>
  </div>
</div>
const dropdown = document.querySelector('.dropdown');
const toggle = dropdown.querySelector('.dropdown__toggle');
const menu = dropdown.querySelector('.dropdown__menu');

function setOpen(open) {
  menu.hidden = !open;
  toggle.setAttribute('aria-expanded', String(open));
}

toggle.addEventListener('click', () => {
  setOpen(menu.hidden);
});

document.addEventListener('pointerdown', (event) => {
  if (!menu.hidden && !dropdown.contains(event.target)) {
    setOpen(false);
  }
});

Почему так:

  • Проверяется вся обёртка .dropdown, а не только меню. Иначе нажатие на кнопку сначала закроет меню как «клик снаружи», а затем обработчик кнопки снова его откроет.
  • contains() возвращает true и для самого элемента, и для любого вложенного: иконки, текста, ссылки.
  • pointerdown срабатывает для мыши, пальца и пера сразу при нажатии. С click есть подвох: если пользователь начал нажатие внутри меню, например выделяя текст, и отпустил снаружи, событие придёт на общего родителя, и меню неожиданно закроется.
  • Атрибут hidden убирает меню и из отображения, и из порядка фокуса.

2. Закрытие по Escape и уходу фокуса

Для клавиатуры нужны отдельные обработчики: Escape закрывает меню, а Tab позволяет перейти к следующему элементу страницы. Добавь этот код после основного скрипта:

dropdown.addEventListener('keydown', (event) => {
  if (event.key === 'Escape' && !menu.hidden) {
    setOpen(false);
    toggle.focus();
  }
});

dropdown.addEventListener('focusout', (event) => {
  if (event.relatedTarget && !dropdown.contains(event.relatedTarget)) {
    setOpen(false);
  }
});

После Escape фокус возвращается на кнопку открытия. В обработчике focusout свойство relatedTarget указывает элемент, который получает фокус. Если он находится вне обёртки, меню закрывается.

Значение null пропускаем: оно бывает, например, при нажатии на часть меню, которая не получает фокус. Нажатие снаружи уже обрабатывается через pointerdown.

Проверка клика вне элемента в Shadow DOM

При событиях из Shadow DOM свойство target может указывать на хост компонента. Для открытого Shadow DOM проверяй composedPath(). Следующий обработчик заменяет прежний pointerdown, а не добавляется к нему. Закрытое shadow-дерево скрывает внутренние узлы от внешнего обработчика.

document.addEventListener('pointerdown', (event) => {
  if (!menu.hidden && !event.composedPath().includes(dropdown)) {
    setOpen(false);
  }
});

3. Без JavaScript: атрибут popover

Для меню, подсказок и небольших панелей в браузерах есть встроенный механизм. Элемент с атрибутом popover сам закрывается по клику снаружи и по Escape, показывается поверх остального контента, а кнопка получает связь с ним для скринридеров:

<button type="button" popovertarget="site-menu">Меню</button>

<div id="site-menu" popover>
  <a href="/blog/">Блог</a>
  <a href="/contacts/">Контакты</a>
</div>

Проверь поддержку Popover API в браузерах своей аудитории по таблице в документации MDN. Старым браузерам может понадобиться запасной вариант.

Открытый поповер находится в верхнем слое: z-index и overflow: hidden родителей его не ограничивают. По умолчанию он центрируется в окне; положение рядом с кнопкой нужно задать отдельно через CSS.

Обычный popover закрывается по нажатию снаружи и Escape. Уход фокуса по Tab сам по себе его не закрывает. Для режима без автоматического закрытия используй popover="manual" и отдельную кнопку управления.

Ошибки при закрытии меню по клику снаружи

  • event.stopPropagation() внутри меню, чтобы «клик не дошёл до документа». Это ломает другие обработчики на странице: аналитику, закрытие других меню.
  • Прозрачный слой на весь экран под меню. Он перехватывает прокрутку и требует отдельной работы с фокусом. Для модальных окон есть <dialog> с showModal().
  • Обработчик на документе добавляется при каждом открытии и не удаляется. Достаточно одного постоянного обработчика с проверкой menu.hidden, как в примере.

Если меню открывается по наведению, на сенсорных экранах учти особенности :hover — о них статья про :hover и :active на мобильных.

Документация

Есть вопрос или дополнение?

Обсудить в Telegram

Добавить комментарий