Как я могу обнаружить щелчок за пределами элемента?

У меня есть несколько HTML-меню, которые я показываю полностью, когда пользователь нажимает на заголовок этих меню. Я хотел бы скрыть эти элементы, когда пользователь щелкает за пределами области меню.

Возможно ли что-то подобное с jQuery?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});

93 ответа

Решение

ПРИМЕЧАНИЕ. Использование stopEventPropagation() это то, чего следует избегать, поскольку оно нарушает нормальный поток событий в DOM. Смотрите эту статью для получения дополнительной информации. Попробуйте использовать этот метод вместо

Прикрепите событие щелчка к телу документа, которое закрывает окно. Прикрепите отдельное событие щелчка к контейнеру, который останавливает распространение в теле документа.

$(window).click(function() {
//Hide the menus if visible
});

$('#menucontainer').click(function(event){
    event.stopPropagation();
});

Вы можете прослушать событие клика на document а затем убедитесь, #menucontainer не является предком или целью выбранного элемента с помощью .closest(),

Если это не так, то выбранный элемент находится за пределами #menucontainer и вы можете смело скрывать это.

$(document).click(function(event) { 
  $target = $(event.target);
  if(!$target.closest('#menucontainer').length && 
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

Редактировать - 2017-06-23

Вы также можете очистить после прослушивателя событий, если вы планируете закрыть меню и хотите прекратить прослушивание событий. Эта функция будет очищать только недавно созданного слушателя, сохраняя любые другие слушатели щелчка на document, С синтаксисом ES2015:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    $target = $(event.target);
    if (!$target.closest(selector).length && $(selector).is(':visible')) {
        $(selector).hide();
        removeClickListener();
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener)
  }

  document.addEventListener('click', outsideClickListener)
}

Редактировать - 2018-03-11

Для тех, кто не хочет использовать jQuery. Вот приведенный выше код на простом vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener)
    }

    document.addEventListener('click', outsideClickListener)
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js 

ПРИМЕЧАНИЕ: это основано на комментарии Алекса, чтобы просто использовать !element.contains(event.target) вместо части jQuery.

Но element.closest() теперь также доступен во всех основных браузерах (версия W3C немного отличается от версии jQuery). Полифиллы можно найти здесь: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

Как обнаружить щелчок за пределами элемента?

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

Я хотел бы скрыть эти элементы, когда пользователь щелкает за пределами области меню.

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

Подсказка: это слово "клик"!

Вы на самом деле не хотите связывать обработчики кликов.

Если вы связываете обработчики кликов, чтобы закрыть диалоговое окно, вы уже потерпели неудачу. Причина, по которой вы потерпели неудачу, заключается в том, что не все запускают click События. Пользователи, не использующие мышь, смогут выйти из вашего диалогового окна (и ваше всплывающее меню, возможно, является типом диалога), нажав клавишу Tab, и они не смогут читать содержимое, находящееся за диалоговым окном, без последующего запуска click событие.

Итак, давайте перефразируем вопрос.

Как закрыть диалог, когда пользователь покончил с этим?

Это цель. К сожалению, теперь нам нужно связать userisfinishedwiththedialog событие, и это связывание не так просто.

Итак, как мы можем определить, что пользователь закончил использовать диалог?

focusout событие

Хорошее начало - определить, покинул ли фокус диалог.

Подсказка: будьте осторожны с blur событие, blur не распространяется, если событие было связано с пузырьковой фазой!

JQuery-х focusout все будет хорошо. Если вы не можете использовать JQuery, то вы можете использовать blur на этапе захвата:

element.addEventListener('blur', ..., true);
//                       use capture: ^^^^

Кроме того, для многих диалогов вам нужно позволить контейнеру получить фокус. добавлять tabindex="-1" чтобы позволить диалогу динамически получать фокус без прерывания потока табуляции.

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Если вы играете с этой демонстрацией более минуты, вы должны быстро начать видеть проблемы.

Во-первых, ссылка в диалоговом окне не активна. Попытка щелкнуть по нему или открыть вкладку приведет к закрытию диалогового окна до того, как произойдет взаимодействие. Это потому, что фокусировка внутреннего элемента вызывает focusout событие до запуска focusin событие снова.

Исправление состоит в том, чтобы поставить в очередь изменение состояния в цикле событий. Это можно сделать с помощью setImmediate(...), или же setTimeout(..., 0) для браузеров, которые не поддерживают setImmediate, После постановки в очередь он может быть отменен последующим focusin:

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>

Вторая проблема заключается в том, что диалоговое окно не закрывается при повторном нажатии ссылки. Это связано с тем, что диалоговое окно теряет фокус, вызывая поведение закрытия, после чего щелчок ссылки запускает диалоговое окно для повторного открытия.

Как и в предыдущем выпуске, необходимо управлять состоянием фокуса. Учитывая, что изменение состояния уже было поставлено в очередь, это просто вопрос обработки событий фокуса на триггерах диалога:

Это должно выглядеть знакомо
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Клавиша Esc

Если вы думали, что справились с состояниями фокуса, вы можете сделать еще больше, чтобы упростить работу с пользователем.

Это часто "приятно иметь" функцию, но обычно когда у вас есть модальное или всплывающее окно любого рода, клавиша Esc закрывает его.

keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('active');
      e.preventDefault();
    }
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Если вы знаете, что в диалоге есть фокусируемые элементы, вам не нужно фокусировать диалог напрямую. Если вы создаете меню, вы можете вместо этого сфокусировать первый элемент меню.

click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}

$('.menu__link').on({
  click: function (e) {
    $(this.hash)
      .toggleClass('submenu--active')
      .find('a:first')
      .focus();
    e.preventDefault();
  },
  focusout: function () {
    $(this.hash).data('submenuTimer', setTimeout(function () {
      $(this.hash).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('submenuTimer'));  
  }
});

$('.submenu').on({
  focusout: function () {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('submenuTimer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('submenu--active');
      e.preventDefault();
    }
  }
});
.menu {
  list-style: none;
  margin: 0;
  padding: 0;
}
.menu:after {
  clear: both;
  content: '';
  display: table;
}
.menu__item {
  float: left;
  position: relative;
}

.menu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}
.menu__link:hover,
.menu__link:focus {
  background-color: black;
  color: lightblue;
}

.submenu {
  border: 1px solid black;
  display: none;
  left: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 100%;
}
.submenu--active {
  display: block;
}

.submenu__item {
  width: 150px;
}

.submenu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}

.submenu__link:hover,
.submenu__link:focus {
  background-color: black;
  color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="menu">
  <li class="menu__item">
    <a class="menu__link" href="#menu-1">Menu 1</a>
    <ul class="submenu" id="menu-1" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
  <li class="menu__item">
    <a  class="menu__link" href="#menu-2">Menu 2</a>
    <ul class="submenu" id="menu-2" tabindex="-1">
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#1">Example 1</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#2">Example 2</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#3">Example 3</a></li>
      <li class="submenu__item"><a class="submenu__link" href="http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
</ul>
lorem ipsum <a href="http://example.com/">dolor</a> sit amet.


Поддержка ролей WAI-ARIA и других специальных возможностей

Надеемся, что этот ответ охватывает основы доступной поддержки клавиатуры и мыши для этой функции, но, поскольку он уже довольно значительный, я собираюсь избегать любого обсуждения ролей и атрибутов WAI-ARIA, однако я настоятельно рекомендую разработчикам обратиться к спецификации для получения подробной информации. на какие роли они должны использовать и любые другие соответствующие атрибуты.

Другие решения здесь не работают для меня, поэтому мне пришлось использовать:

if(!$(event.target).is('#foo'))
{
    // hide menu
}

Сейчас 2020 год, и вы можете использовать event.composedPath()

От: https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath

Метод createdPath() интерфейса Event возвращает путь к событию, который представляет собой массив объектов, для которых будут вызваны слушатели.

         const target = document.querySelector('#myTarget')

document.addEventListener('click', (event) => {
  const withinBoundaries = event.composedPath().includes(target)

  if (withinBoundaries) {
    target.innerText = 'Click happened inside element'
  } else {
    target.innerText = 'Click happened **OUTSIDE** element'
  } 
})
         /* just to make it good looking. you don't need this */
#myTarget {
  margin: 50px auto;
  width: 500px;
  height: 500px;
  background: gray;
  border: 10px solid black;
}
         <div id="myTarget">
  click me (or not!)
</div>

У меня есть приложение, которое работает аналогично примеру Эрана, за исключением того, что я прикрепляю событие click к телу при открытии меню... Вроде как:

$('#menucontainer').click(function(event) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

Больше информации о jQuery's one() функция

После исследования я нашел три рабочих решения (я забыл ссылки на страницы для справки)

Первое решение

<script>
    //The good thing about this solution is it doesn't stop event propagation.

    var clickFlag = 0;
    $('body').on('click', function () {
        if(clickFlag == 0) {
            console.log('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.log('showed the element');
        /* Show the element */
    });
</script>

Второе решение

<script>
    $('body').on('click', function(e) {
        if($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

Третье решение

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

У меня работает просто отлично.

Теперь для этого есть плагин: внешние события ( запись в блоге)

Следующее происходит, когда обработчик clickoutside (WLOG) привязан к элементу:

  • элемент добавляется в массив, который содержит все элементы с обработчиками clickoutside
  • обработчик кликапространстве имен) привязан к документу (если его там еще нет)
  • при любом щелчке в документе событие clickoutside запускается для тех элементов в этом массиве, которые не равны или являются родительскими для цели click -events
  • Кроме того, в качестве event.target для события clickoutside задается элемент, по которому щелкнул пользователь (так что вы даже знаете, что щелкнул пользователь, а не только то, что он щелкнул снаружи)

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

У меня это отлично сработало!!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});

Простое решение для ситуации:

$(document).mouseup(function (e)
{
    var container = $("YOUR SELECTOR"); // Give you class or ID

    if (!container.is(e.target) &&            // If the target of the click is not the desired div or section
        container.has(e.target).length === 0) // ... nor a descendant-child of the container
    {
        container.hide();
    }
});

Приведенный выше скрипт скроет div если за пределами div событие клика срабатывает.

Вы можете увидеть следующий блог для получения дополнительной информации: http://www.codecanal.com/detect-click-outside-div-using-javascript/

Я не думаю, что вам действительно нужно закрывать меню, когда пользователь нажимает снаружи; вам нужно, чтобы меню закрывалось, когда пользователь щелкает в любом месте на странице. Если вы нажмете на меню, или от меню оно должно закрыться правильно?

Не найдя удовлетворительных ответов выше, я попросил написать этот пост на днях. Для более педантичных, есть ряд ошибок, на которые стоит обратить внимание:

  1. Если вы прикрепляете обработчик события click к элементу body во время щелчка, обязательно дождитесь второго щелчка, прежде чем закрыть меню и отменить привязку события. В противном случае событие щелчка, открывшее меню, будет всплывать перед слушателем, который должен закрыть меню.
  2. Если вы используете event.stopPropogation() для события щелчка, никакие другие элементы на вашей странице не могут иметь функцию щелчка в любом месте, чтобы закрыть.
  3. Прикрепление обработчика события click к элементу body на неопределенный срок не является эффективным решением
  4. Сравнение цели события и его родителей с создателем обработчика предполагает, что вы хотите закрыть меню, когда вы щелкаете по нему, а когда вы действительно хотите, это закрыть его, когда вы щелкаете в любом месте страницы.
  5. Прослушивание событий в элементе body сделает ваш код более хрупким. Стайлинг так невинен, как это сломает его: body { margin-left:auto; margin-right: auto; width:960px;}

Как сказал другой автор, есть много ошибок, особенно если отображаемый элемент (в данном случае меню) имеет интерактивные элементы. Я нашел следующий метод достаточно надежным:

$('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
        //check up the tree of the click target to check whether user has clicked outside of menu
        if ($(event.target).parents('#menuscontainer').length==0) {
            // your code to hide menu

            //this event listener has done its job so we can unbind it.
            $(this).unbind(event);
        }

    })
});

Я удивлен, что никто на самом деле не признал focusout событие:

var button = document.getElementById('button');
button.addEventListener('click', function(e){
  e.target.style.backgroundColor = 'green';
});
button.addEventListener('focusout', function(e){
  e.target.style.backgroundColor = '';
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <button id="button">Click</button>
</body>
</html>

Проверьте цель события щелчка окна (она должна распространяться на окно, если она не захвачена где-либо еще), и убедитесь, что это не какой-либо из элементов меню. Если это не так, то вы находитесь за пределами вашего меню.

Или проверьте положение щелчка и посмотрите, содержится ли оно в области меню.

Solution1

Вместо использования event.stopPropagation(), который может иметь побочные эффекты, просто определите переменную простого флага и добавьте ее if состояние. Я проверил это и работал должным образом без каких-либо побочных эффектов stopPropagation:

var flag = "1";
$('#menucontainer').click(function(event){
    flag = "0"; // flag 0 means click happened in the area where we should not do any action
});

$('html').click(function() {
    if(flag != "0"){
        // Hide the menus if visible
    }
    else {
        flag = "1";
    }
});

Solution2

Просто if состояние:

$(document).on('click', function(event){
    var container = $("#menucontainer");
    if (!container.is(event.target) &&            // If the target of the click isn't the container...
        container.has(event.target).length === 0) // ... nor a descendant of the container
    {
        // Do whatever you want to do when click is outside the element
    }
});

2020 с использованием ближайшего метода нативного JSAPI.

document.addEventListener('click', ({ target }) => {
  if (!target.closest('.el1, .el2, #el3')) {
    alert('click outside')
  }
})

У меня был успех с чем-то вроде этого:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

Логика такова: когда #menuscontainer привязать обработчик щелчка к телу, которое скрыто #menuscontainer только если цель (клика) не является его дочерней.

Событие имеет свойство с именем event.path элемента, которое представляет собой "статический упорядоченный список всех его предков в древовидном порядке". Чтобы проверить, произошло ли событие из определенного элемента DOM или одного из его дочерних элементов, просто проверьте путь для этого конкретного элемента DOM. Он также может быть использован для проверки нескольких элементов путем логического OR Проверка элемента в some функция.

$("body").click(function() {
  target = document.getElementById("main");
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  })
  if (flag) {
    console.log("Inside")
  } else {
    console.log("Outside")
  }
});
#main {
  display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
  <ul>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
  </ul>
</div>
<div id="main2">
  Outside Main
</div>

Так что для вашего случая это должно быть

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});

Используйте для доступности

Здесь есть один ответ, который говорит (совершенно правильно), что сосредоточение внимания на clickevents - это проблема доступности, поскольку мы хотим обслуживать пользователей клавиатуры. Событие - это правильная вещь для использования здесь, но это можно сделать гораздо проще, чем в другом ответе (и в чистом javascript тоже):

Более простой способ сделать это:

«Проблема» с использованием focusoutзаключается в том, что если элемент внутри вашего диалога / модального окна / меню теряет фокус, на что-то также «внутри» событие все равно будет запущено. Мы можем проверить, что это не так, посмотрев на event.relatedTarget (который сообщает нам, какой элемент получит фокус).

      dialog = document.getElementById("dialogElement")

dialog.addEventListener("focusout", function (event) {
    if (
        // we are still inside the dialog so don't close
        dialog.contains(event.relatedTarget) ||
        // we have switched to another tab so probably don't want to close 
        !document.hasFocus()  
    ) {
        return;
    }
    dialog.close();  // or whatever logic you want to use to close
});

К вышесказанному есть одна небольшая ошибка: relatedTarget может быть null. Это нормально, если пользователь щелкает за пределами диалогового окна, но будет проблемой, если пользователь не щелкнет внутри диалогового окна и диалоговое окно не будет фокусируемым. Чтобы исправить это, вы должны установить tabIndex=0 так что ваш диалог сфокусирован.

Как вариант:

var $menu = $('#menucontainer');
$(document).on('click', function (e) {

    // If element is opened and click target is outside it, hide it
    if ($menu.is(':visible') && !$menu.is(e.target) && !$menu.has(e.target).length) {
        $menu.hide();
    }
});

Он не имеет проблем с остановкой распространения событий и лучше поддерживает несколько меню на одной странице, где нажатие на второе меню, когда открыто первое, оставляет первое открытие в решении stopPropagation.

Вот ванильное решение JavaScript для будущих зрителей.

Если щелкнуть любой элемент в документе, если идентификатор выбранного элемента переключен или скрытый элемент не скрыт, а скрытый элемент не содержит выбранный элемент, переключите элемент.

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();
<a href="javascript:void(0)" id="toggle">Toggle Hidden Div</a>
<div id="hidden" style="display: none;">This content is normally hidden. click anywhere other than this content to make me disappear</div>

Если у вас будет несколько переключателей на одной странице, вы можете использовать что-то вроде этого:

  1. Добавьте имя класса hidden на складной предмет.
  2. При щелчке документа закройте все скрытые элементы, которые не содержат выбранный элемент и не скрыты
  3. Если выбранный элемент является переключателем, переключите указанный элемент.

(function () {
    "use strict";
    var hiddenItems = document.getElementsByClassName('hidden'), hidden;
    document.addEventListener('click', function (e) {
        for (var i = 0; hidden = hiddenItems[i]; i++) {
            if (!hidden.contains(e.target) && hidden.style.display != 'none')
                hidden.style.display = 'none';
        }
        if (e.target.getAttribute('data-toggle')) {
            var toggle = document.querySelector(e.target.getAttribute('data-toggle'));
            toggle.style.display = toggle.style.display == 'none' ? 'block' : 'none';
        }
    }, false);
})();
<a href="javascript:void(0)" data-toggle="#hidden1">Toggle Hidden Div</a>
<div class="hidden" id="hidden1" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden2">Toggle Hidden Div</a>
<div class="hidden" id="hidden2" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden3">Toggle Hidden Div</a>
<div class="hidden" id="hidden3" style="display: none;" data-hidden="true">This content is normally hidden</div>

Я нашел этот метод в каком-то плагине календаря jQuery.

function ClickOutsideCheck(e)
{
  var el = e.target;
  var popup = $('.popup:visible')[0];
  if (popup==undefined)
    return true;

  while (true){
    if (el == popup ) {
      return true;
    } else if (el == document) {
      $(".popup").hide();
      return false;
    } else {
      el = $(el).parent()[0];
    }
  }
};

$(document).bind('mousedown.popup', ClickOutsideCheck);

Если вы пишете сценарии для IE и FF 3.* и хотите просто узнать, произошел ли щелчок в определенной области окна, вы также можете использовать что-то вроде:

this.outsideElementClick = function(objEvent, objElement){   
var objCurrentElement = objEvent.target || objEvent.srcElement;
var blnInsideX = false;
var blnInsideY = false;

if (objCurrentElement.getBoundingClientRect().left >= objElement.getBoundingClientRect().left && objCurrentElement.getBoundingClientRect().right <= objElement.getBoundingClientRect().right)
    blnInsideX = true;

if (objCurrentElement.getBoundingClientRect().top >= objElement.getBoundingClientRect().top && objCurrentElement.getBoundingClientRect().bottom <= objElement.getBoundingClientRect().bottom)
    blnInsideY = true;

if (blnInsideX && blnInsideY)
    return false;
else
    return true;}

Использование:

var go = false;
$(document).click(function(){
    if(go){
        $('#divID').hide();
        go = false;
    }
})

$("#divID").mouseover(function(){
    go = false;
});

$("#divID").mouseout(function (){
    go = true;
});

$("btnID").click( function(){
    if($("#divID:visible").length==1)
        $("#divID").hide(); // Toggle
    $("#divID").show();
});

Вместо этого используйте прерывание потока, событие размытия / фокуса или любую другую хитрую технику, просто сопоставьте поток событий с родством элемента:

$(document).on("click.menu-outside", function(event){
    // Test if target and it's parent aren't #menuscontainer
    // That means the click event occur on other branch of document tree
    if(!$(event.target).parents().andSelf().is("#menuscontainer")){
        // Click outisde #menuscontainer
        // Hide the menus (but test if menus aren't already hidden)
    }
});

Чтобы удалить щелчок вне слушателя события, просто:

$(document).off("click.menu-outside");

Если кому-то интересно, вот решение javascript (es6):

window.addEventListener('mouseup', e => {
        if (e.target != yourDiv && e.target.parentNode != yourDiv) {
            yourDiv.classList.remove('show-menu');
            //or yourDiv.style.display = 'none';
        }
    })

и es5, на всякий случай:

window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
    yourDiv.classList.remove('show-menu'); 
    //or yourDiv.style.display = 'none';
}

});

Вот простое решение с помощью чистого JavaScript. Это актуально с ES6:

var isMenuClick = false;
var menu = document.getElementById('menuscontainer');
document.addEventListener('click',()=>{
    if(!isMenuClick){
       //Hide the menu here
    }
    //Reset isMenuClick 
    isMenuClick = false;
})
menu.addEventListener('click',()=>{
    isMenuClick = true;
})

Я использовал приведенный ниже скрипт и сделал с JQuery.

jQuery(document).click(function(e) {
    var target = e.target; //target div recorded
    if (!jQuery(target).is('#tobehide') ) {
        jQuery(this).fadeOut(); //if the click element is not the above id will hide
    }
})

Ниже найдите код HTML

<div class="main-container">
<div> Hello I am the title</div>
<div class="tobehide">I will hide when you click outside of me</div>
</div>

Вы можете прочитать учебник здесь

Upvote для самого популярного ответа, но добавить

&& (e.target != $('html').get(0)) // ignore the scrollbar

Итак, щелчок на полосе прокрутки не [скрывает или что-то еще] ваш целевой элемент.

Другие вопросы по тегам