如何检测元素外的点击?

Posted

技术标签:

【中文标题】如何检测元素外的点击?【英文标题】:How do I detect a click outside an element? 【发布时间】:2010-09-14 06:09:40 【问题描述】:

我有一些 html 菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外点击时,我想隐藏这些元素。

用 jQuery 可以实现这样的事情吗?

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

【问题讨论】:

以下是此策略的示例:jsfiddle.net/tedp/aL7Xe/1 正如汤姆所说,在使用这种方法之前,您需要阅读css-tricks.com/dangers-stopping-event-propagation。不过那个 jsfiddle 工具很酷。 获取对元素的引用,然后是 event.target,最后 != 或 == 他们都相应地执行代码.. 尝试使用event.path。 http://***.com/questions/152975/how-do-i-detect-a-click-outside-an-element/43405204#43405204 Vanilla JS solution 带 event.target不带 event.stopPropagation 【参考方案1】:

注意:应该避免使用stopPropagation,因为它会破坏 DOM 中的正常事件流。请参阅this CSS Tricks article 了解更多信息。考虑改用this method。

将单击事件附加到关闭窗口的文档正文。将单独的单击事件附加到容器,以停止传播到文档正文。

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

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

【讨论】:

这会破坏#menucontainer 中包含的许多事物的标准行为,包括按钮和链接。我很惊讶这个答案如此受欢迎。 这不会破坏#menucontainer 中任何东西的行为,因为它位于其中任何东西的传播链的底部。 它非常漂亮,但您应该使用$('html').click() 而不是正文。正文始终具有其内容的高度。内容不多或者屏幕很高的情况下,只对body填充的部分有效。 我也很惊讶这个解决方案获得了如此多的选票。对于外部具有 stopPropagation jsfiddle.net/Flandre/vaNFw/3 的任何元素,这将失败 Philip Walton 很好地解释了为什么这个答案不是最佳解决方案:css-tricks.com/dangers-stopping-event-propagation【参考方案2】:

您可以在document 上侦听click 事件,然后使用.closest() 确保#menucontainer 不是被点击元素的祖先或目标。

如果不是,则点击的元素在#menucontainer 之外,您可以安全地隐藏它。

$(document).click(function(event)  
  var $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) => 
    const $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 

注意: 这是基于 Alex 的评论,只使用 !element.contains(event.target) 而不是 jQuery 部分。

但是element.closest() 现在也可以在所有主流浏览器中使用(W3C 版本与 jQuery 版本略有不同)。 Polyfill 可以在这里找到:Element.closest()

编辑 – 2020-05-21

如果您希望用户能够在元素内部单击并拖动,然后在元素外部释放鼠标,而不关闭元素:

      ...
      let lastMouseDownX = 0;
      let lastMouseDownY = 0;
      let lastMouseDownWasOutside = false;

      const mouseDownListener = (event: MouseEvent) => 
        lastMouseDownX = event.offsetX
        lastMouseDownY = event.offsetY
        lastMouseDownWasOutside = !$(event.target).closest(element).length
      
      document.addEventListener('mousedown', mouseDownListener);

outsideClickListener:

const outsideClickListener = event => 
        const deltaX = event.offsetX - lastMouseDownX
        const deltaY = event.offsetY - lastMouseDownY
        const distSq = (deltaX * deltaX) + (deltaY * deltaY)
        const isDrag = distSq > 3
        const isDragException = isDrag && !lastMouseDownWasOutside

        if (!element.contains(event.target) && isVisible(element) && !isDragException)  // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
          document.removeEventListener('mousedown', mouseDownListener); // Or add this line to removeClickListener()
        
    

【讨论】:

我尝试了许多其他答案,但只有这个有效。谢谢。我最终使用的代码是这样的: $(document).click( function(event) if( $(event.target).closest('.window').length == 0 ) $('.window' ).fadeOut('fast'); ); 我实际上最终选择了这个解决方案,因为它更好地支持同一页面上的多个菜单,其中在第一个菜单打开时单击第二个菜单将使第一个菜单在 stopPropagation 解决方案中保持打开状态。 优秀的答案。当您有多个要关闭的项目时,这是要走的路。 不使用 jQuery - !element.contains(event.target) 使用 Node.contains()【参考方案3】:

如何检测元素外的点击?

这个问题如此受欢迎和有这么多答案的原因是它看起来很复杂。经过近八年和几十个答案,我真的很惊讶看到对可访问性的关注如此之少。

当用户在菜单区域外点击时,我想隐藏这些元素。

这是一个崇高的事业,是实际的问题。问题的标题(大多数答案似乎试图解决的问题)包含一个不幸的红鲱鱼。

提示:是单词“点击”

您实际上并不想绑定点击处理程序。

如果您绑定点击处理程序来关闭对话框,那么您已经失败了。你失败的原因是不是每个人都会触发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>

如果您使用该演示超过一分钟,您应该很快就会发现问题。

首先是对话框中的链接不可点击。尝试单击它或使用选项卡将导致对话框在交互发生之前关闭。这是因为聚焦内部元素会在再次触发focusin 事件之前触发focusout 事件。

解决方法是在事件循环中对状态更改进行排队。对于不支持setImmediate 的浏览器,可以使用setImmediate(...)setTimeout(..., 0) 来完成此操作。一旦排队,它可以被后续的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 roles and attributes,但我强烈建议实施者请参阅规范,了解他们应该使用哪些角色以及任何其他适当属性的详细信息。

【讨论】:

这是最完整的答案,并考虑了解释和可访问性。我认为这应该是公认的答案,因为大多数其他答案只处理点击,只是代码 sn-p 没有任何解释。 You don't actually want to bind click handlers. 您可以绑定点击处理程序,也可以处理用户没有鼠标的情况。它不会损害可访问性,它只会为使用鼠标的用户添加功能。向一组用户添加功能不会伤害无法使用该功能的用户。你可以提供不止一种方法来关闭一个diablog 这实际上是一个很常见的逻辑谬误。即使其他人没有受益,也可以为一组用户提供一项功能。我同意所有用户都应该能够获得良好的体验 @ICW,通过使用blurfocusout 处理程序,您仍将完全支持鼠标和触摸用户,并且它具有支持键盘用户的额外好处。我从来没有建议你应该支持鼠标用户。【参考方案4】:

这里的其他解决方案对我不起作用,所以我不得不使用:

if(!$(event.target).is('#foo'))

    // hide menu

编辑:纯 javascript 变体 (2021-03-31)

我使用这种方法来处理在单击外部时关闭下拉菜单。

首先,我为组件的所有元素创建了一个自定义类名。此类名称将添加到构成菜单小部件的所有元素中。

const className = `dropdown-$Date.now()-$Math.random() * 100`;

我创建了一个函数来检查点击和被点击元素的类名。如果单击的元素不包含我上面生成的自定义类名,则应将show 标志设置为false,然后菜单将关闭。

const onClickOutside = (e) => 
  if (!e.target.className.includes(className)) 
    show = false;
  
;

然后我将点击处理程序附加到窗口对象。

// add when widget loads
window.addEventListener("click", onClickOutside);

...最后是一些家务

// remove listener when destroying the widget
window.removeEventListener("click", onClickOutside);

【讨论】:

这对我有用,除了我在IF 语句中添加了&amp;&amp; !$(event.target).parents("#foo").is("#foo"),这样任何子元素在单击时都不会关闭菜单。【参考方案5】:

我有一个与 Eran 的示例类似的应用程序,除了我在打开菜单时将点击事件附加到正文...有点像这样:

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

  event.stopPropagation();
);

更多关于jQuery's one() function的信息

【讨论】:

但是如果你点击菜单本身,然后在外面,它将不起作用:)【参考方案6】:

现在是 2020 年,您可以使用 event.composedPath()

发件人:https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath

Event 接口的composedPath() 方法返回事件的路径,这是一个将调用监听器的对象数组。

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>

【讨论】:

非常感谢!【参考方案7】:

经过研究,我找到了三个可行的解决方案(我忘记了参考页面链接)

第一个解决方案

<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>

【讨论】:

第三种解决方案是迄今为止最优雅的检查方式。它也不涉及 jQuery 的任何开销。很不错。它有很大帮助。谢谢。【参考方案8】:
$("#menuscontainer").click(function() 
    $(this).focus();
);
$("#menuscontainer").blur(function()
    $(this).hide();
);

对我来说很好用。

【讨论】:

如果尝试将其与自定义构建的选择和选项菜单一起使用,则会在单击之前触发模糊,因此不会选择任何内容【参考方案9】:

现在有一个插件:outside events (blog post)

clickoutside 处理程序 (WLOG) 绑定到元素时会发生以下情况:

元素被添加到一个数组中,该数组包含所有带有 clickoutside 处理程序的元素 一个 (namespaced) click 处理程序绑定到文档(如果还没有的话) 在文档中的任何 click 上,clickoutside 事件会针对该数组中不等于 click-事件目标 此外,clickoutside 事件的 event.target 设置为用户点击的元素(因此您甚至可以知道用户点击了什么,而不仅仅是他点击了外部)

因此不会停止传播任何事件,并且可以在带有外部处理程序的元素“上方”使用额外的 click 处理程序。

【讨论】:

【参考方案10】:

这对我来说非常有用!

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

【讨论】:

【参考方案11】:

这种情况的简单解决方案是:

$(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/

【讨论】:

【参考方案12】:

我不认为你真正需要的是在用户点击外部时关闭菜单;您需要的是当用户单击页面上的任何位置时关闭菜单。如果您单击菜单,或关闭菜单,它应该关闭吗?

在上面没有找到满意的答案促使我前几天写了this blog post。对于更迂腐的人,有一些问题需要注意:

    如果在单击时将单击事件处理程序附加到正文元素,请务必等待第二次单击,然后再关闭菜单并取消绑定事件。否则,打开菜单的点击事件将冒泡到必须关闭菜单的侦听器。 如果您对点击事件使用 event.stopPropogation(),则页面中的任何其他元素都不能具有 click-anywhere-to-close 功能。 将单击事件处理程序无限期地附加到正文元素并不是一种高效的解决方案 将事件的目标及其父事件与处理程序的创建者进行比较假定您想要的是在您单击菜单时关闭它,而您真正想要的是在您单击页面上的任意位置时关闭它。 监听 body 元素上的事件将使您的代码更加脆弱。像这样无辜的造型会破坏它:body margin-left:auto; margin-right: auto; width:960px;

【讨论】:

"如果您点击菜单,或者关闭菜单,它应该关闭吗?"不总是。通过拖出元素来取消单击仍将触发文档级单击,但目的不是继续关闭菜单。还有许多其他类型的对话框可以使用允许在内部单击的“点击”行为。【参考方案13】:

正如另一位发帖者所说,有很多陷阱,特别是如果您正在显示的元素(在本例中为菜单)具有交互元素。 我发现以下方法相当稳健:

$('#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);
        

    )
);

【讨论】:

【参考方案14】:

检查窗口点击事件目标(它应该传播到窗口,只要它没有在其他任何地方捕获),并确保它不是任何菜单元素。如果不是,那么你就在你的菜单之外。

或者检查点击的位置,看看它是否包含在菜单区域内。

【讨论】:

【参考方案15】:

解决方案1

不要使用可能有一些副作用的 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";
    
);

解决方案2

只需一个简单的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
    
);

【讨论】:

我将此解决方案与布尔标志一起使用,它也适用于铰接的 DOm,如果在#menucontainer 内部还有很多其他元素 解决方案 1 效果更好,因为它可以处理在事件传播到文档时点击目标从 DOM 中删除的情况。【参考方案16】:

我在这样的事情上取得了成功:

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 的主体。

【讨论】:

【参考方案17】:

我很惊讶没有人真正承认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>

【讨论】:

【参考方案18】:

该事件有一个名为 event.path 的属性,它是一个“按树顺序排列其所有祖先的静态有序列表”。要检查事件是否源自特定 DOM 元素或其子元素之一,只需检查该特定 DOM 元素的路径。它还可以用于通过在some 函数中逻辑上ORing 元素检查来检查多个元素。

$("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;
  background:yellow;
<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
  
);

【讨论】:

【参考方案19】:

作为一个变体:

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();
    
);

stopping event propagation 没有问题,并且更好地支持同一页面上的多个菜单,其中在第一个菜单打开时单击第二个菜单将使第一个菜单在 stopPropagation 解决方案中保持打开状态。

【讨论】:

【参考方案20】:

我在一些 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);

【讨论】:

【参考方案21】:

这是面向未来观众的原生 JavaScript 解决方案。

点击文档中的任意元素时,如果被点击元素的id被切换,或者被隐藏的元素没有被隐藏且被隐藏的元素不包含被点击的元素,则切换该元素。

(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>

如果您要在同一页面上进行多个切换,您可以使用以下内容:

    将类名hidden 添加到可折叠项。 单击文档时,关闭所有不包含被单击元素且未被隐藏的隐藏元素 如果单击的元素是切换元素,则切换指定元素。

(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>

【讨论】:

【参考方案22】:

如果您正在为 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;

【讨论】:

【参考方案23】:

而不是使用流中断、模糊/焦点事件或任何其他棘手的技术,只需将事件流与元素的亲属关系匹配:

$(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");

【讨论】:

【参考方案24】:

用途:

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();
);

【讨论】:

【参考方案25】:

如果有人好奇这里是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';

);

【讨论】:

【参考方案26】:

这是一个纯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;
)

【讨论】:

"Up-to-date with ES6" 是一个相当大胆的声明,因为 ES6 中唯一最新的东西是 () =&gt; 而不是 function() 。你所拥有的被归类为带有 ES6 扭曲的纯 JavaScript。 @MortenMoulder:是的。即使它实际上是 ES6,也只是为了引起注意。但只要看看解决方案。我觉得挺好的。 It's vanilla JS and works for event target removed from DOM (e.g. when value from inner popup is selected, immediately closing the popup).来自我的 +1!【参考方案27】:

我使用了下面的脚本并用 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>

你可以阅读教程here

【讨论】:

【参考方案28】:

2020 解决方案使用原生 JS API closest 方法。

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

【讨论】:

【参考方案29】:

使用focusout 获取可访问性

这里有一个答案说(非常正确)关注click 事件是一个可访问性问题,因为我们想要满足键盘用户的需求。 focusout 事件在这里使用是正确的,但它可以比其他答案更简单(在纯 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 以便您的对话框具有焦点。

【讨论】:

这是迄今为止最好的解决方案,因为它考虑了可访问性。【参考方案30】:
$(document).click(function() 
    $(".overlay-window").hide();
);
$(".overlay-window").click(function() 
    return false;
);

如果您单击文档,则隐藏给定元素,除非您单击同一元素。

【讨论】:

以上是关于如何检测元素外的点击?的主要内容,如果未能解决你的问题,请参考以下文章

如何访问函数外的数组元素

检测主线程外的 UI 操作

检测鼠标在子窗口外的点击

更新组件外的元素

VoiceOver不能在横向滚动到屏幕外的元素?

检测用户是不是点击了弹出窗口中的元素