我有一些HTML菜单,当用户点击这些菜单的头部时,我完全显示这些菜单。我想在用户点击菜单以外的地方隐藏这些元素。
用jQuery可以实现这样的功能吗?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
在文档主体上附加一个点击事件,关闭窗口。给容器附加一个单独的点击事件,停止传播到文档主体。
$(window).click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
你可以监听document
上的click事件,然后通过使用.closest()
确保#menucontainer
不是被点击元素的祖先或目标。
如果不是,那么被点击的元素就在 "#menucontainer "之外,你可以安全地隐藏它。
$(document).click(function(event) {
$target = $(event.target);
if(!$target.closest('#menucontainer').length &&
$('#menucontainer').is(":visible")) {
$('#menucontainer').hide();
}
});
如果你打算解散菜单并想停止监听事件,你也可以在事件监听器之后进行清理。这个函数将只清理新创建的监听器,保留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)
}
对于那些不想使用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的版本有一些不同)。
Polyfills可以在这里找到。Element.closest()。
如何检测元素外的点击?
这个问题之所以如此受欢迎,答案如此之多,是因为它复杂得令人费解。 经过近8年的时间和几十个答案,我真的很惊讶于对可访问性的关注是如此之少。
我想在用户点击菜单之外的时候隐藏这些元素'。 区域。
这是一个崇高的事业,是*实际的问题。 问题的标题—是大多数答案似乎试图解决的问题—包含一个不幸的红鲱鱼。
提示。 它'是"点击"!这个词。
如果你'绑定点击处理程序来关闭对话框,你'已经失败了。
你失败的原因是,不是每个人都会触发点击
事件。
不使用鼠标的用户将能够通过按下Tab来逃离你的对话框(而你的弹出式菜单可以说是对话框的一种类型),然后他们将无法读取对话框后面的内容,而不会随后触发click
事件。
所以让我们'重新表述一下这个问题。
当用户完成一个对话框的时候,如何关闭它?
这就是我们的目标。
不幸的是,现在我们需要绑定userisfinishedwiththedialog
事件,而这个绑定并不'那么简单。
那么我们如何才能检测到用户已经使用完一个对话框呢?
focusout
事件一个好的开始是确定焦点是否已经离开对话框。
提示。
小心使用blur
事件,如果事件被绑定到冒泡阶段,blur
不会传播!。
jQuery'的focusout
就可以了。
如果你不能使用jQuery,那么你可以在捕捉阶段使用blur
。
element.addEventListener('blur', ..., true);
// use capture: ^^^^
另外,对于许多对话框,你需要允许容器获得焦点。
添加tabindex="-1"
以允许对话框动态地获得焦点,而不会以其他方式中断标签流程。
<!--开始片段。 js hide: false console.true babel: true true babel.false --> -- begin snippet: js hide: false console: true false -->
$('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>
如果你玩那个演示超过一分钟,你应该很快就会开始看到问题。
首先是对话框中的链接无法点击。 试图点击它或tab到它将导致对话框在交互发生之前关闭。 这是因为在再次触发 "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'));
}
});
<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
$('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'));
}
});
<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
$('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 键将其关闭。
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('active');
e.preventDefault();
}
}
<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
$('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();
}
<!--开始片段。 js hide: true console: true babel.false --> -- begin snippet: js hide: true console: true false -->
$('.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 角色和属性][2],不过我*强烈建议实现者参考规范,了解他们应该使用什么角色和任何其他适当属性的细节。
1:
我有一个应用程序,其工作原理与Eran'的例子类似,只是我在打开菜单时将点击事件附加到主体上......有点像这样。
$('#menucontainer').click(function(event) {
$('html').one('click',function() {
// Hide the menus
});
event.stopPropagation();
});
关于jQuery'的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>
现在有了一个插件。 [外部事件][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
正如另一个海报所说,有很多小麻烦,特别是如果你要显示的元素(在这种情况下是一个菜单)有交互式元素。 我发现下面的方法相当稳健。
$('#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);
}
})
});
对于这种情况,一个简单的解决方法就是。
$(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/
不使用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";
}
});
只需一个简单的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
}
});
我'用这样的东西获得了成功。
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
。
当 "#menuscontainer "显示时,绑定一个点击处理程序到主体上,只有当(点击的)目标不是它的子代时,才会隐藏 "#menuscontainer"。
作为一种变体。
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();
}
});
它在[停止事件传播][1]方面没有问题,并且更好地支持同一页面上的多个菜单,在stopPropagation解决方案中,当第一个菜单打开时,点击第二个菜单会让第一个菜单打开。
[1]: http://css-tricks.com/dangers-stopping-event-propagation/
我在某个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);
事件有一个叫做元素的event.path的属性,这个属性是一个"静态有序的列表,其中包含了它的所有祖先的树形顺序"。
要检查一个事件是否源于一个特定的DOM元素或它的一个子元素,只需检查该特定DOM元素的路径。
它也可以通过在some
函数中逻辑地或
元素检查来检查多个元素。
<!--开始片段。 js hide: false console: true babel.false --> -- begin snippet: js hide: false console: true false -->
$("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
}
});
下面是香草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);
})();
<!--开始片段。 js hide.true --> -- begin snippet: js hide: true -->
(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>
<!--结束代码段-->
如果你要在同一个页面上有多个toggles,你可以使用这样的东西。
hidden
添加到可折叠项中。当文档点击后,关闭所有不包含点击的元素且不隐藏的隐藏元素; 3.
如果点击的元素是toggle,则切换指定的元素。
<!--开始片段。 js hide: false -->
(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>
<!--结束片段-->
我很惊讶居然没有人承认focusout
事件。
<!--开始片段。 js hide: false console: true babel.false --> -- begin snippet: js hide: false console: true false -->
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>
<!--结束片段-->