如何检测元素外的点击?

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

是这样的jQuery可能吗?

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

注:使用stopEventPropagation()是应该避免的,因为它打破了DOM中的正常事件stream。 有关更多信息,请参阅此文章 。 考虑使用这种方法 。

将单击事件附加到closures窗口的文档主体。 将一个单独的单击事件附加到停止传播到文档主体的窗口。

 $(window).click(function() { //Hide the menus if visible }); $('#menucontainer').click(function(event){ event.stopPropagation(); }); 

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

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

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

编辑 – 2017-06-23

如果您打算closures菜单并想要停止监听事件,也可以在事件监听器之后进行清理。 该函数只会清理新创build的侦听器,保留document上的其他任何点击侦听器。 使用ES2015语法:

 export function hideOnClickOutside(selector) { const outsideClickListener = (event) => { if (!$(event.target).closest(selector).length) { if ($(selector).is(':visible')) { $(selector).hide() removeClickListener() } } } const removeClickListener = () => { document.removeEventListener('click', outsideClickListener) } document.addEventListener('click', outsideClickListener) } 

如何检测元素外的点击?

这个问题如此受欢迎,答案如此之多的原因是它看似复杂。 经过近八年的时间和几十个回答,我真的很惊讶地发现,对可达性的关心很less。

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

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

提示:这是“点击”一词!

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

如果你绑定点击处理程序closures对话框,你已经失败了。 你失败的原因是不是每个人都触发click事件。 不使用鼠标的用户将能够通过按Tab键来跳出对话框(并且popup菜单可以说是一种对话框),然后他们将无法读取对话框后面的内容,而不会随后触发click事件。

所以我们来重述一下这个问题。

用户完成后如何closures对话框?

这是目标。 不幸的是,现在我们需要绑定用户完成的userisfinishedwiththedialog事件,而绑定并不那么简单。

那么我们如何检测到用户已经完成了对话?

focusout事件

一个好的开始是确定焦点是否离开对话。

提示:注意blur事件,如果事件被绑定到冒泡阶段, blur不会传播!

jQuery的focusout将做得很好。 如果你不能使用jQuery,那么你可以在捕获阶段使用blur

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

另外,对于许多对话框,您需要允许容器获得焦点。 添加tabindex="-1"允许对话框dynamic接收焦点,而不会中断标签stream。

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

这里的其他解决scheme不适合我,所以我不得不使用:

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

我有一个类似于Eran的例子的应用程序,除了当我打开菜单时,将点击事件附加到身体…有点像这样:

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

更多关于jQuery的one()函数的信息

 $("#menuscontainer").click(function() { $(this).focus(); }); $("#menuscontainer").blur(function(){ $(this).hide(); }); 

为我工作就好了。

现在有一个插件: 外部事件 ( 博客文章 )

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

  • 该元素被添加到一个数组,其中包含所有元素与clickoutside处理程序
  • ( 名称空间 ) 点击处理程序绑定到文档(如果不是已经存在的话)
  • 在文档中的任何点击clickoutside事件触发该数组中的那些元素不等于或单击事件目标的父
  • 此外, clickoutside事件的event.target被设置为用户点击的元素(所以你甚至知道用户点击了什么,而不仅仅是他在外面点击)

因此,没有任何事件被阻止传播,并且可以在外部处理器的“元素上面”使用额外的点击处理程序。

这对我完美的工作!

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

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

上面找不到满意的答案促使我前几天写这篇博文 。 对于更迂腐,有一些需要注意的问题:

  1. 如果在点击时将单击事件处理程序附加到body元素,请确保在closures菜单之前等待第二次点击并解除绑定事件。 否则,打开菜单的单击事件将会冒泡到必须closures菜单的侦听器。
  2. 如果在click事件上使用event.stopPropogation(),则页面中的其他元素不能具有点击任何位置closuresfunction。
  3. 无限期地将点击事件处理程序附加到body元素不是一个高性能的解决scheme
  4. 将事件的目标及其父母与处理程序的创build者进行比较时,假定您想要的是closures菜单,而当您单击页面上的任意位置时,您真正想要closures该菜单。
  5. 监听body元素上的事件会使你的代码变得更脆弱。 造型无辜,因为这会打破它: body { margin-left:auto; margin-right: auto; width:960px;} 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); } }) }); 

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

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

经过研究,我find了三个工作scheme(我忘了页面链接供参考)

首先解决scheme

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

第二种scheme

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

第三个scheme

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

作为一个变种:

 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解决scheme中。

解决方法1

而不是使用可能有一些副作用的event.stopPropagation(),只需定义一个简单的标志variables,并添加一个条件。 我testing了这个工作,没有任何副作用停止传播:

 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

只要一个简单的条件:

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

我已经有了这样的成功:

 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的子项时,才会将点击处理程序绑定到隐藏#menuscontainer的正文。

我在一些jQuery日历插件中find了这个方法。

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

一个简单的解决scheme是:

 $(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 click事件以外,上面的脚本将隐藏div

您可以看到以下博客获取更多信息: http : //www.codecanal.com/detect-click-outside-div-using-javascript/

相反,使用stream量中断,模糊/焦点事件或任何其他棘手的技术,只需将事件stream与元素的亲缘关系匹配:

 $(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解决scheme。

在单击文档中的任何元素时,如果单击的元素的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> 

该事件有一个名为event.path的属性,它是一个“以树状顺序排列的所有祖先的静态有序列表” 。 要检查一个事件是从一个特定的DOM元素还是它的一个子元素发起的,只要检查这个特定DOM元素的path。 It can also be used to check multiple elements by logically OR ing the element check in the some function.

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

If you are scripting for IE and FF 3.* and you just want to know if the click occured within a certain box area, you could also use something like:

 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).click(function() { $(".overlay-window").hide(); }); $(".overlay-window").click(function() { return false; }); 

If you click on the document, hide a given element, unless you click on that same element.

Hook a click event listener on the document. Inside the event listener, you can look at the event object , in particular, the event.target to see what element was clicked:

 $(document).click(function(e){ if ($(e.target).closest("#menuscontainer").length == 0) { // .closest can help you determine if the element // or one of its ancestors is #menuscontainer console.log("hide"); } }); 

Upvote for the most popular answer, but add

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

so, a click on a scroll bar does not [hide or whatever] your target element.

This is my solution to this problem:

 $(document).ready(function() { $('#user-toggle').click(function(e) { $('#user-nav').toggle(); e.stopPropagation(); }); $('body').click(function() { $('#user-nav').hide(); }); $('#user-nav').click(function(e){ e.stopPropagation(); }); }); 

I ended up doing something like this:

 $(document).on('click', 'body, #msg_count_results .close',function() { $(document).find('#msg_count_results').remove(); }); $(document).on('click','#msg_count_results',function(e) { e.preventDefault(); return false; }); 

I have a close button within the new container for end users friendly UI purposes. I had to use return false in order to not go through. Of course, having an A HREF on there to take you somewhere would be nice, or you could call some ajax stuff instead. Either way, it works ok for me. 正是我想要的。

We implemented a solution, partly based off a comment from a user above, which works perfectly for us. We use it to hide a search box / results when clicking outside those elements, excluding the element that originally.

 // HIDE SEARCH BOX IF CLICKING OUTSIDE $(document).click(function(event){ // IF NOT CLICKING THE SEARCH BOX OR ITS CONTENTS OR SEARCH ICON if ($("#search-holder").is(":visible") && !$(event.target).is("#search-holder *, #search")) { $("#search-holder").fadeOut('fast'); $("#search").removeClass('active'); } }); 

It checks if the search box is already visible first also, and in our case, it's also removing an active class on the hide/show search button.

function:

 $(function() { $.fn.click_inout = function(clickin_handler, clickout_handler) { var item = this; var is_me = false; item.click(function(event) { clickin_handler(event); is_me = true; }); $(document).click(function(event) { if (is_me) { is_me = false; } else { clickout_handler(event); } }); return this; } }); 

用法:

 this.input = $('<input>') .click_inout( function(event) { me.ShowTree(event); }, function() { me.Hide(); } ) .appendTo(this.node); 

And functions are very simple:

 ShowTree: function(event) { this.data_span.show(); } Hide: function() { this.data_span.hide(); } 

I did it like this in YUI 3:

 // Detect the click anywhere other than the overlay element to close it. Y.one(document).on('click', function (e) { if (e.target.ancestor('#overlay') === null && e.target.get('id') != 'show' && overlay.get('visible') == true) { overlay.hide(); } }); 

I am checking if ancestor is not the widget element container,
if target is not which open the widget/element,
if widget/element I want to close is already open (not that important).