Как отследить клик вне элемента

Хорошая практика для удобства пользователя реализовать закрытие динамического элемента не только при клике на кнопку, а также при клике вне этого элемента

В примере ниже, при нажатии на кнопку Nav отображается блок навигации. Его можно закрыть при клике на кнопку Nav или при клике в любом месте за пределами блока навигации


HTML

Структура HTML данного примера выглядит следующим образом

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title></title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>
    <div class="header">
      <div class="container">
        <div class="header__flex">
          <div class="header__logo">Logo</div>
          <div class="header__button">Nav</div>
        </div>
        <div class="header__nav nav">
          <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">About</a></li>
            <li><a href="#">Portfolio</a></li>
            <li><a href="#">Contact</a></li>
          </ul>
        </div>
      </div>
    </div>
    <script src="js/main.js"></script>
  </body>
</html>


CSS

Напишем стили

Для плавного появления и исчезания окна навигации используем комбинацию свойств transition, opacity и visibility

Чтобы при закрытии окно навигации скрывалось не мгновенно для свойства visibility применяем задержку 0.6s transition: visibility 0s 0.6s, opacity 0.6s ease;, то есть окно навигации скрывается полностью только после того, как отработает плавное исчезание через свойство opacity

.container {
  max-width: 1140px;
  margin: 0 auto;
  padding: 0 15px;
}
.header {
  padding: 15px 0;
  background: #485696;
  border-radius: 15px;
}
.header .container {
  position: relative;
}
.header__flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.header__logo {
  font-size: 24px;
  font-weight: bold;
  color: #fff;
}
.header__button {
  background: #fff;
  height: 48px;
  border-radius: 15px;
  padding: 0 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #485696;
  position: relative;
  font-size: 18px;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  transition: 0.3s ease;
  border: 2px solid transparent;
}
.header__button:hover {
  background: #485696;
  color: #fff;
  border-color: #fff;
}
.nav {
  position: absolute;
  top: calc(100% + 30px);
  right: 15px;
  background: #485696;
  border-radius: 25px;
  font-size: 18px;
  padding: 15px 0;
  visibility: hidden;
  transition: visibility 0s 0.6s, opacity 0.6s ease; /* при закрытии окна навигации, сначала плавно понижается непрозрачность - opacity: 0; затем мгновенно исчезает через 0.6s ожидания - visibility: hidden; */
  opacity: 0;
  overflow: hidden;
}
.nav_active {
  transition: visibility 0s 0s, opacity 0.6s ease; /* при открытии окна навигации, оно сначала мгновенно появляется - visibility: visible;, затем плавно повышается непрозрачность - opacity: 1; */
  opacity: 1;
  visibility: visible;
}
.nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
}
.nav ul li a {
  display: block;
  padding: 5px 48px;
  white-space: nowrap;
  color: #fff;
  text-decoration: none;
  transition: 0.3s ease;
}
.nav ul li a:hover {
  background: #6372b4;
}
.nav ul li a:active {
  background: #8a95c7;
}


JavaScript

Напишем логику на JavaScript

e.target - элемент, на котором произошло определенное событие, в данном случае - клик

closest() - этот метод ищет ближайший родительский элемент по заданному селектору

Ниже код с комментариями

document.addEventListener('DOMContentLoaded', () => { // Структура страницы загружена и готова к взаимодействию

  const button = document.querySelector('.header__button') // находим кнопку для открытия/закрытия окна навигации
  const nav = document.querySelector('.nav') // находим окно навигации

  button.addEventListener('click', () => { // при клике на кнопку
    nav.classList.toggle('nav_active') // открываем/закрываем окно навигации, добаляя/удаляя активный класс
  })

  window.addEventListener('click', e => { // при клике в любом месте окна браузера
    const target = e.target // находим элемент, на котором был клик
    if (!target.closest('.nav') && !target.closest('.header__button')) { // если этот элемент или его родительские элементы не окно навигации и не кнопка
      nav.classList.remove('nav_active') // то закрываем окно навигации, удаляя активный класс
    }
  })

})