Как определить клик вне элемента на JavaScript (без jQuery)?
Правильный способ: делегирование события на document и проверка target
Вы на верном пути. Ключевая идея — вешать обработчик клика на document, но внутри него проверять, что цель события (event.target) не является вашим элементом и не находится внутри него. Для этого используется метод .contains().
Главная проблема, которую вы заметили — клик по кнопке открытия также вызывает закрытие. Решается она двумя способами:
- Отмена всплытия события на кнопке (
event.stopPropagation()), чтобы клик по кнопке не доходил доdocument. - Проверка в самом обработчике, что клик был не по кнопке и не по дропдауну.
Я рекомендую второй способ как более надёжный (он не ломает естественное всплытие событий). Вот полное решение:
const menuBtn = document.getElementById('menuBtn');
const dropdown = document.getElementById('myDropdown');
// Открытие меню
menuBtn.addEventListener('click', function(event) {
// Если меню уже открыто — можно закрыть (toggle)
const isOpen = dropdown.style.display === 'block';
dropdown.style.display = isOpen ? 'none' : 'block';
});
// Закрытие при клике вне меню
document.addEventListener('click', function(event) {
const target = event.target;
// Проверяем, что клик был НЕ по кнопке И НЕ внутри дропдауна
const isClickInsideMenu = dropdown.contains(target);
const isClickOnButton = menuBtn.contains(target);
if (!isClickInsideMenu && !isClickOnButton) {
dropdown.style.display = 'none';
}
});
Как это работает?
element.contains(target)— возвращаетtrue, еслиtargetявляется самим элементом или его дочерним узлом (на любом уровне вложенности).- Мы проверяем оба условия: клик не по кнопке И не по меню. Только тогда закрываем.
- Обработчик на
documentсрабатывает после всплытия события, поэтому если клик был по кнопке —isClickOnButtonбудетtrue, и закрытие не произойдёт.
Более универсальная версия (функция-помощник)
Если вам нужно использовать эту логику для нескольких элементов на странице, вынесите её в отдельную функцию:
function onClickOutside(element, callback) {
document.addEventListener('click', function(event) {
if (!element.contains(event.target)) {
callback();
}
});
}
// Использование:
onClickOutside(dropdown, function() {
dropdown.style.display = 'none';
});
Важно: В этой упрощённой версии не учтена кнопка открытия. Если вы её используете, нужно либо исключить кнопку из проверки, либо передавать её вторым аргументом.
Советы для реальных проектов
- Удаляйте обработчик при уничтожении компонента (например, в SPA), чтобы избежать утечек памяти.
- Для модальных окон часто используют дополнительную проверку на клавишу Escape.
- В современных фреймворках (React, Vue) есть встроенные решения (хуки, директивы), но на чистом JS этот подход остаётся стандартом.
Этот паттерн используется в тысячах проектов и считается «best practice» для чистого JavaScript. Он работает во всех современных браузерах, включая IE11 (с полифиллом для .contains()).
Альтернативный подход: использование события focusout / blur
Если ваш выпадающий элемент содержит фокусируемые элементы (ссылки, кнопки, инпуты), можно использовать событие focusout (или blur с задержкой). Этот метод особенно удобен для тултипов и небольших попапов, которые появляются при фокусе.
const dropdown = document.getElementById('myDropdown');
// Когда фокус уходит с элемента или его потомков
dropdown.addEventListener('focusout', function(event) {
// Используем setTimeout, чтобы дать браузеру время переключить фокус
setTimeout(() => {
// Если новый фокус не внутри dropdown — закрываем
if (!dropdown.contains(document.activeElement)) {
dropdown.style.display = 'none';
}
}, 0);
});
Плюсы:
- Не нужно вешать глобальный обработчик на
document. - Естественно работает с навигацией через Tab.
Минусы:
- Не сработает, если кликнуть по пустому месту (не по фокусируемому элементу).
- Требует, чтобы меню содержало фокусируемые элементы.
- Может быть нестабильным из-за
setTimeout.
Этот метод стоит использовать как дополнение к основному (по клику), если ваше меню поддерживает клавиатурную навигацию.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги