如何检测元素外的点击?
我有一些HTML菜单,当用户点击这些菜单的头部时,我完全显示这些菜单。我想在用户点击菜单以外的地方隐藏这些元素。
用jQuery可以实现这样的功能吗?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
2356
20
我有一些HTML菜单,当用户点击这些菜单的头部时,我完全显示这些菜单。我想在用户点击菜单以外的地方隐藏这些元素。
用jQuery可以实现这样的功能吗?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
在文档主体上附加一个点击事件,关闭窗口。给容器附加一个单独的点击事件,停止传播到文档主体。
你可以监听
document
上的click事件,然后通过使用.closest()
确保#menucontainer
不是被点击元素的祖先或目标。如果不是,那么被点击的元素就在 "#menucontainer "之外,你可以安全地隐藏它。
编辑 - 2017-06-23
如果你打算解散菜单并想停止监听事件,你也可以在事件监听器之后进行清理。这个函数将只清理新创建的监听器,保留
document
上的任何其他点击监听器。使用ES2015的语法。编辑 - 2018-03-11
对于那些不想使用jQuery的人。下面是上述代码在普通vanillaJS(ECMAScript6)中的应用。
注意: 这是基于Alex的评论,只用
!element.contains(event.target)
来代替jQuery部分。但是
element.closest()
现在也可以在所有的主要浏览器中使用(W3C的版本与jQuery的版本有一些不同)。 Polyfills可以在这里找到。Element.closest()。这个问题之所以如此受欢迎,答案如此之多,是因为它复杂得令人费解。 经过近8年的时间和几十个答案,我真的很惊讶于对可访问性的关注是如此之少。
这是一个崇高的事业,是*实际的问题。 问题的标题—是大多数答案似乎试图解决的问题—包含一个不幸的红鲱鱼。
提示。 它'是"点击"!这个词。
你其实并不想绑定点击处理程序。
如果你'绑定点击处理程序来关闭对话框,你'已经失败了。 你失败的原因是,不是每个人都会触发
点击
事件。 不使用鼠标的用户将能够通过按下Tab来逃离你的对话框(而你的弹出式菜单可以说是对话框的一种类型),然后他们将无法读取对话框后面的内容,而不会随后触发click
事件。所以让我们'重新表述一下这个问题。
这就是我们的目标。 不幸的是,现在我们需要绑定
userisfinishedwiththedialog
事件,而这个绑定并不'那么简单。那么我们如何才能检测到用户已经使用完一个对话框呢?
focusout
事件一个好的开始是确定焦点是否已经离开对话框。
提示。 小心使用
blur
事件,如果事件被绑定到冒泡阶段,blur
不会传播!。jQuery'的
focusout
就可以了。 如果你不能使用jQuery,那么你可以在捕捉阶段使用blur
。另外,对于许多对话框,你需要允许容器获得焦点。 添加
tabindex="-1"
以允许对话框动态地获得焦点,而不会以其他方式中断标签流程。<!--开始片段。 js hide: false console.true babel: true true babel.false --> -- begin snippet: js hide: false console: true false -->
如果你玩那个演示超过一分钟,你应该很快就会开始看到问题。
首先是对话框中的链接无法点击。 试图点击它或tab到它将导致对话框在交互发生之前关闭。 这是因为在再次触发 "focusin "事件之前,聚焦内部元素会触发一个 "focusout "事件。
解决的办法是在事件循环上排队等待状态变化。 这可以通过使用
setImmediate(...)
,或者对于不支持setImmediate
的浏览器使用setTimeout(..., 0)
来实现。 一旦排队,就可以被后续的focusin
取消。<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
<!--结束片段-->。
第二个问题是,当再次按下链接时,对话框不会关闭。 这是因为对话框失去了焦点,触发了关闭行为,之后点击链接会触发对话框重新打开。
与上一个问题类似,需要对焦点状态进行管理。 鉴于状态变化已经被排队,所以只需要处理对话框触发器上的焦点事件即可。
这看起来应该很熟悉
<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
Esc
钥匙
如果你以为处理了焦点状态就完事了,你还可以做更多的事情来简化用户体验。
这通常是一个"不错的功能"。 功能,但常见的是,当你有一个模态或任何形式的弹出窗口时,Esc 键将其关闭。
<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
如果你知道你在对话框内有可聚焦的元素,你不需要直接聚焦对话框。 如果你正在构建一个菜单,你可以将第一个菜单项作为焦点。
<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
WAI-ARIA的作用和其他无障碍支持。
本回答希望能涵盖该功能的无障碍键盘和鼠标支持的基本知识,但由于它已经相当可观,我将避免讨论 [WAI-ARIA 角色和属性][2],不过我*强烈建议实现者参考规范,了解他们应该使用什么角色和任何其他适当属性的细节。
1:
[2]: https://www.w3.org/TR/wai-aria/
这里的其他解决方案对我不起作用,所以我只好用。
我有一个应用程序,其工作原理与Eran'的例子类似,只是我在打开菜单时将点击事件附加到主体上......有点像这样。
关于jQuery'的
one()
函数的更多信息经过研究,我找到了三种可行的解决方案(我忘记了页面链接,供参考)。
第一个解决方案
第二种解决方案
第三种解决方案
对我来说,工作就很好。
现在有了一个插件。 [外部事件][1] ([博客文章][2])
当一个clickoutside处理程序(WLOG)被绑定到一个元素时,会发生以下情况。
所以没有事件被停止传播,可以使用额外的点击处理程序"上面"。 元素的外部处理程序。
[1]: https://github.com/cowboy/jquery-outside-events [2]: http://benalman.com/projects/jquery-outside-events-plugin/ [3]: http://docs.jquery.com/Namespaced_Events
这对我来说是完美的工作!
我不'认为你真正需要的是当用户点击外面时关闭菜单。 你需要的是菜单关闭时,用户点击任何地方在所有的页面上。 如果你点击了菜单,或者离开了菜单,就应该关闭了吧?
以上没有找到满意的答案,促使我前几天写了【这篇博文】[1]。 对于比较迂腐的人来说,有一些小毛病需要注意。
如果你在点击事件上使用event.stopPropogation(),你的页面中的其他元素就不能有点击任意关闭的功能。
将点击事件处理程序无限期地附加到body元素上不是一个性能良好的解决方案。
将事件的目标,以及它的父代与处理程序'的创建者进行比较,假设你想要的是当你点击掉菜单时关闭菜单,而你真正想要的是当你点击页面上的任何地方时关闭它。
监听body元素上的事件会让你的代码更脆。 像这样无辜的样式会破坏它。
body { margin-left:auto; margin-right: auto; width:960px;}
[1]: http://programming34m0.blogspot.com/2011/05/simplifying-javascript-jump-menu.html
正如另一个海报所说,有很多小麻烦,特别是如果你要显示的元素(在这种情况下是一个菜单)有交互式元素。 我发现下面的方法相当稳健。
对于这种情况,一个简单的解决方法就是。
以上脚本将隐藏
div
之外的div
点击事件。你可以看下面的博客了解更多信息。 http://www.codecanal.com/detect-click-outside-div-using-javascript/
解决方案1
不使用event.stopPropagation(),因为它可能会有一些副作用,只需定义一个简单的标志变量,并添加一个
if
条件。 我测试了一下,工作正常,没有任何stopPropagation的副作用。解决方案2
只需一个简单的
if
条件。检查窗口点击事件的目标(它应该传播到窗口,只要它没有被其他地方捕获),并确保它不是任何菜单元素。 如果不是,那么你就在你的菜单之外。
或者检查点击的位置,看看它是否包含在菜单区域内。
我'用这样的东西获得了成功。
其逻辑是:当
#menuscontainer
显示时,将一个点击处理程序绑定到主体上,只有当目标(点击的)不是#menuscontainer
的子代时,才会隐藏#menuscontainer
。 当 "#menuscontainer "显示时,绑定一个点击处理程序到主体上,只有当(点击的)目标不是它的子代时,才会隐藏 "#menuscontainer"。作为一种变体。
它在[停止事件传播][1]方面没有问题,并且更好地支持同一页面上的多个菜单,在stopPropagation解决方案中,当第一个菜单打开时,点击第二个菜单会让第一个菜单打开。
[1]: http://css-tricks.com/dangers-stopping-event-propagation/
我在某个jQuery日历插件中发现了这个方法。
事件有一个叫做元素的event.path的属性,这个属性是一个"静态有序的列表,其中包含了它的所有祖先的树形顺序"。 要检查一个事件是否源于一个特定的DOM元素或它的一个子元素,只需检查该特定DOM元素的路径。 它也可以通过在
some
函数中逻辑地或
元素检查来检查多个元素。<!--开始片段。 js hide: false console: true babel.false --> -- begin snippet: js hide: false console: true false -->
<!--结束片段-->
所以对于你的情况来说,应该是
下面是香草JavaScript的解决方案,供以后的观众参考。
当点击文档中的任何元素时,如果被点击的元素'的id被切换,或者隐藏的元素没有被隐藏,且隐藏的元素不包含被点击的元素,则切换该元素。
<!--开始片段。 js hide.true --> -- begin snippet: js hide: true -->
<!--结束代码段-->
如果你要在同一个页面上有多个toggles,你可以使用这样的东西。
hidden
添加到可折叠项中。当文档点击后,关闭所有不包含点击的元素且不隐藏的隐藏元素; 3.
如果点击的元素是toggle,则切换指定的元素。
<!--开始片段。 js hide: false -->
<!--结束片段-->
我很惊讶居然没有人承认
focusout
事件。<!--开始片段。 js hide: false console: true babel.false --> -- begin snippet: js hide: false console: true false -->
<!--结束片段-->