我有一些HTML菜单,当用户单击这些菜单的标题时,它们会完整显示。当用户在菜单区域之外单击时,我想隐藏这些元素。
jQuery可能会发生这种情况吗?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
我有一些HTML菜单,当用户单击这些菜单的标题时,它们会完整显示。当用户在菜单区域之外单击时,我想隐藏这些元素。
jQuery可能会发生这种情况吗?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
Answers:
注意:
stopEventPropagation()
应该避免使用,因为它会破坏DOM中的正常事件流。有关更多信息,请参见本文。考虑使用这种方法,而不是
将单击事件附加到关闭窗口的文档主体。将单独的click事件附加到容器,以停止传播到文档主体。
$(window).click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
$('html').click()
身体。身体始终具有其内容的高度。它内容不多或屏幕非常高,仅适用于身体填充的部分。
您可以使用侦听click事件document
,然后确保#menucontainer
它不是其祖先或被单击元素的目标 .closest()
。
如果不是,则单击的元素在的外部,#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的人。这是上面的纯香草代码(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()
如果希望用户能够在元素内部单击并拖动,然后在元素外部释放鼠标,而无需关闭该元素:
...
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()
}
}
!element.contains(event.target)
用Node.contains()
如何检测元素外部的点击?
这个问题之所以如此流行并且答案如此之多,是因为它看似复杂。经过将近八年的时间和数十个答案,我真的很惊讶地看到对可访问性的关注很少。
当用户在菜单区域之外单击时,我想隐藏这些元素。
这是一个崇高的原因,是实际的问题。问题的标题(即大多数答案似乎试图解决的问题)包含不幸的鲱鱼。
提示:这是“点击”一词!
如果要绑定单击处理程序以关闭对话框,则您已经失败了。您失败的原因是,并非所有人都触发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({
focusout: function () {
$(this.hash).data('timer', setTimeout(function () {
$(this.hash).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this.hash).data('timer'));
}
});
如果您认为已经通过处理焦点状态完成了操作,则可以做更多的事情来简化用户体验。
这通常是一个“很不错”的功能,但是很常见的是,当您拥有任何形式的模式或弹出窗口时,Esc键会将其关闭。
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('active');
e.preventDefault();
}
}
如果知道对话框中有可聚焦的元素,则无需直接聚焦对话框。如果要构建菜单,则可以将焦点放在第一个菜单项上。
click: function (e) {
$(this.hash)
.toggleClass('submenu--active')
.find('a:first')
.focus();
e.preventDefault();
}
这个答案有望涵盖此功能可访问的键盘和鼠标支持的基础知识,但是由于它已经相当大了,因此我将避免任何有关WAI-ARIA角色和属性的讨论,但是我强烈建议实现者参考该规范以获取详细信息。他们应该使用什么角色以及任何其他适当的属性。
这里的其他解决方案对我不起作用,因此我不得不使用:
if(!$(event.target).is('#foo'))
{
// hide menu
}
&& !$(event.target).parents("#foo").is("#foo")
在IF
语句中添加了内容,以便在单击任何子元素时都不会关闭菜单。
.is('#foo, #foo *')
,但是我不建议绑定单击处理程序解决这个问题。
!$(event.target).closest("#foo").length
会更好,并且不需要添加@honyovk。
我有一个与Eran的示例类似的应用程序,除了打开菜单时将click事件附加到主体上。
$('#menucontainer').click(function(event) {
$('html').one('click',function() {
// Hide the menus
});
event.stopPropagation();
});
.one
内部使用- 绑定后,$('html')
编写一个$('html').off('click')
。
one
处理器会自动调用off
(因为它在jQuery的文档显示真实)。
经过研究,我找到了三种可行的解决方案(我忘记了页面链接以供参考)
<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>
document.getElementsByClassName
,如果有人有线索请分享。
$("#menuscontainer").click(function() {
$(this).focus();
});
$("#menuscontainer").blur(function(){
$(this).hide();
});
对我有用。
#menucontainer
,问题是关于点击的问题
tabindex="-1"
到#menuscontainer
使其工作。看来,如果将输入标签放在容器中并单击它,则容器将被隐藏。
将clickoutside处理程序(WLOG)绑定到元素时,将发生以下情况:
因此,不会阻止任何事件的传播,并且可以在外部处理程序的“上方”使用其他单击处理程序。
$( '#element' ).on( 'clickoutside', function( e ) { .. } );
我不认为您真正需要的是在用户单击外部时关闭菜单。您需要的是,当用户单击页面上的任意位置时,菜单关闭。如果单击菜单或关闭菜单,它应该关闭吗?
上面找不到满意的答案,促使我前几天写了这篇博客。对于更加学究的人来说,有许多陷阱需要注意:
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);
}
})
});
一个简单的解决方案是:
$(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();
}
});
上面的脚本将隐藏click事件div
外部div
是否被触发。
您可以查看以下博客以获取更多信息:http : //www.codecanal.com/detect-click-outside-div-using-javascript/
与其使用可能会有一些副作用的event.stopPropagation(),不如定义一个简单的flag变量并添加一个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
仅在(单击的目标)不是它的子级时才隐藏的主体上。
作为变体:
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解决方案中的第一个菜单保持打开状态。
该事件具有元素的名为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;
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
}
});
event.path
不是一回事。
我在某些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);
这是面向未来观众的原始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);
})();
如果您将在同一页面上进行多个切换,则可以使用以下方法:
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>
令我惊讶的是,没有人真正意识到这一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>
如果您正在为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;}
与其使用流程中断,模糊/聚焦事件或任何其他棘手的技术,只需将事件流程与元素的亲属关系相匹配:
$(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");
#menuscontainer
您仍然在经历它的父母。您应该先检查一下,如果不是那个元素,那么就去DOM树。
if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){
。这是一个很小的优化,但在程序生命周期中仅发生几次:对于每次单击,如果click.menu-outside
事件已注册。它更长(+32个字符)并且不使用方法链接
采用:
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();
});
如果有人好奇这里是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;
})
() => {}
代替function() {}
。您所拥有的归类为带有ES6的纯JavaScript。
将单击事件侦听器挂在文档上。在事件侦听器内部,您可以查看事件对象,特别是event.target,以查看单击了哪个元素:
$(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");
}
});
我已经使用下面的脚本并完成了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>
您可以在这里阅读教程
为了更易于使用和更具表现力的代码,我为此创建了一个jQuery插件:
$('div.my-element').clickOut(function(target) {
//do something here...
});
注意: target是用户实际单击的元素。但是回调仍然在原始元素的上下文中执行,因此您可以按照预期在jQuery回调中利用它。
插入:
$.fn.clickOut = function (parent, fn) {
var context = this;
fn = (typeof parent === 'function') ? parent : fn;
parent = (parent instanceof jQuery) ? parent : $(document);
context.each(function () {
var that = this;
parent.on('click', function (e) {
var clicked = $(e.target);
if (!clicked.is(that) && !clicked.parents().is(that)) {
if (typeof fn === 'function') {
fn.call(that, clicked);
}
}
});
});
return context;
};
默认情况下,单击事件侦听器放置在文档上。但是,如果您想限制事件侦听器的范围,则可以传入一个jQuery对象,该对象表示一个父级元素,该元素将是监听点击的顶级父级。这样可以防止不必要的文档级事件侦听器。显然,除非提供的父元素是初始元素的父元素,否则它将无法工作。
像这样使用:
$('div.my-element').clickOut($('div.my-parent'), function(target) {
//do something here...
});