我有一些HTML菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外单击时,我希望隐藏这些元素。
jQuery是否可以实现这样的功能?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
我有一些HTML菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外单击时,我希望隐藏这些元素。
jQuery是否可以实现这样的功能?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
当前回答
我最后做了这样的事情:
$(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;
});
我在新容器中有一个关闭按钮,用于最终用户友好的UI。为了不通过,我不得不使用return false。当然,有一个A HREF带你去某个地方会很好,或者你可以调用一些ajax的东西。不管怎样,这对我来说都很好。这正是我想要的。
其他回答
使用流中断、模糊/聚焦事件或任何其他棘手的技术,只需将事件流与元素的亲属关系匹配即可:
$(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");
如何检测元素外部的单击?
这个问题之所以如此流行并有如此多的答案,是因为它看起来很复杂。在经历了近八年的时间和数十个答案之后,我真的很惊讶地发现,人们对无障碍设施的关注程度如此之低。
当用户在菜单区域外单击时,我希望隐藏这些元素。
这是一项崇高的事业,也是实际问题。大多数答案似乎试图解决的问题的标题包含了一个不幸的红鲱鱼。
提示:就是“咔嚓”这个词!
您实际上不想绑定单击处理程序。
如果要绑定单击处理程序以关闭对话框,则已失败。你失败的原因是不是每个人都会触发点击事件。不使用鼠标的用户可以通过按Tab键退出对话框(弹出菜单可以说是一种对话框),然后他们就无法在不触发单击事件的情况下阅读对话框后面的内容。
所以让我们重新表述这个问题。
当用户完成对话框时,如何关闭对话框?
这就是目标。不幸的是,现在我们需要将用户绑定到对话框事件中,而绑定并不那么简单。
那么,我们如何检测用户是否已完成使用对话框?
聚焦事件
一个好的开始是确定焦点是否已离开对话框。
提示:注意模糊事件,如果事件绑定到冒泡阶段,则模糊不会传播!
jQuery的聚焦功能会很好。如果不能使用jQuery,那么可以在捕获阶段使用blur:
element.addEventListener('blur', ..., true);
// use capture: ^^^^
此外,对于许多对话框,您需要允许容器获得焦点。添加tabindex=“-1”以允许对话框动态接收焦点,而不会中断选项卡流。
$('a').on('click',函数(){$(this.hash).tggleClass('active').focus();});$('div').on('fout',函数(){$(this).removeClass('active');});第二部分{显示:无;}.活动{显示:块;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><a href=“#example”>示例</a><div id=“example”tabindex=“-1”>Lorem ipsum<a href=“http://example.com“>dolor</a>坐下。</div>
如果你玩了一分钟以上的演示,你应该很快开始发现问题。
第一个是对话框中的链接不可单击。尝试单击它或选项卡将导致对话框在交互发生之前关闭。这是因为在再次触发聚焦事件之前,聚焦内部元素会触发聚焦事件。
修复方法是在事件循环中对状态更改进行排队。这可以通过对不支持setImmediate的浏览器使用setImmediat(…)或setTimeout(…,0)来实现。排队后,可通过后续聚焦取消:
$('.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',函数(){$(this.hash).tggleClass('active').focus();});$('div').在({focusout:函数(){$(this).data('timer',setTimeout(函数(){$(this).removeClass('active');}.bind(this),0));},focusin:函数(){clearTimeout($(this).data('timer'));}});第二部分{显示:无;}.活动{显示:块;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><a href=“#example”>示例</a><div id=“example”tabindex=“-1”>Lorem ipsum<a href=“http://example.com“>dolor</a>坐下。</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',函数(){$(this.hash).tggleClass('active').focus();});$('div').在({focusout:函数(){$(this).data('timer',setTimeout(函数(){$(this).removeClass('active');}.bind(this),0));},focusin:函数(){clearTimeout($(this).data('timer'));}});$('a').开({focusout:函数(){$(this.hash).data('timer',setTimeout(函数(){$(this.hash).removeClass('active');}.bind(this),0));},focusin:函数(){clearTimeout($(this.hash).data('timer'));}});第二部分{显示:无;}.活动{显示:块;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><a href=“#example”>示例</a><div id=“example”tabindex=“-1”>Lorem ipsum<a href=“http://example.com“>dolor</a>坐下。</div>
Esc键
如果你认为你已经处理好了焦点状态,那么你可以做更多的事情来简化用户体验。
这通常是一个“很好拥有”的功能,但当您有任何类型的模式或弹出窗口时,Esc键都会关闭它。
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('active');
e.preventDefault();
}
}
$('a').on('click',函数(){$(this.hash).tggleClass('active').focus();});$('div').在({focusout:函数(){$(this).data('timer',setTimeout(函数(){$(this).removeClass('active');}.bind(this),0));},focusin:函数(){clearTimeout($(this).data('timer'));},向下键:函数(e){如果(e.whic===27){$(this).removeClass('active');e.预防违约();}}});$('a').开({focusout:函数(){$(this.hash).data('timer',setTimeout(函数(){$(this.hash).removeClass('active');}.bind(this),0));},focusin:函数(){clearTimeout($(this.hash).data('timer'));}});第二部分{显示:无;}.活动{显示:块;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><a href=“#example”>示例</a><div id=“example”tabindex=“-1”>Lorem ipsum<a href=“http://example.com“>dolor</a>坐下。</div>
如果您知道对话框中有可聚焦元素,则不需要直接聚焦对话框。如果你正在构建一个菜单,你可以把焦点放在第一个菜单项上。
click: function (e) {
$(this.hash)
.toggleClass('submenu--active')
.find('a:first')
.focus();
e.preventDefault();
}
$('.menu__link').上({点击:函数(e){$(this.hash).tggleClass('submenu--active').find('a:first').focus();e.预防违约();},focusout:函数(){$(this.hash).data('subenuTimer',setTimeout(函数(){$(this.hash).removeClass('submenu--active');}.bind(this),0));},focusin:函数(){clearTimeout($(this.hash).data('subenuTimer'));}});$('.submenu').打开({focusout:函数(){$(this).data('subenuTimer',setTimeout(函数(){$(this).removeClass('ssubmenu--active');}.bind(this),0));},focusin:函数(){clearTimeout($(this).data('subenuTimer'));},向下键:函数(e){如果(e.whic===27){$(this).removeClass('ssubmenu--active');e.预防违约();}}});.菜单{列表样式:无;边距:0;填充:0;}.menu:之后{清晰:两者都有;内容:“”;显示:表格;}.menu__项{浮动:左侧;位置:相对;}.menu__链接{背景色:浅蓝色;颜色:黑色;显示:块;衬垫:0.5em 1em;文本装饰:无;}.menu__link:悬停,.menu__link:焦点{背景色:黑色;颜色:浅蓝色;}.子菜单{边框:1px实心黑色;显示:无;左:0;列表样式:无;边距:0;填充:0;位置:绝对;顶部:100%;}.子菜单--活动{显示:块;}.subenu__项{宽度:150px;}.subenu__link{背景色:浅蓝色;颜色:黑色;显示:块;衬垫:0.5em 1em;文本装饰:无;}.subenu__link:悬停,.subenu__link:焦点{背景色:黑色;颜色:浅蓝色;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><ul class=“menu”><li class=“menu__item”>菜单1</a><ul class=“submenu”id=“menu-1”tabindex=“-1”><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#1“>示例1</a></li><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#2“>示例2</a></li><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#3“>示例3</a></li><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#4“>示例4</a></li></ul></li><li class=“menu__item”>菜单2</a><ul class=“submenu”id=“menu-2”tabindex=“-1”><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#1“>示例1</a></li><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#2“>示例2</a></li><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#3“>示例3</a></li><li class=“submenu__item”><a class=“subenu__link”href=“http://example.com/#4“>示例4</a></li></ul></li></ul>lorem ipsum<a href=“http://example.com/“>dolor</a>坐下。
WAI-ARIA角色和其他辅助功能支持
这个答案希望涵盖了对该功能的可访问键盘和鼠标支持的基础知识,但由于它已经相当大,我将避免任何关于WAI-ARIA角色和属性的讨论,但是我强烈建议实现者参考规范,以了解他们应该使用的角色和任何其他适当属性的详细信息。
作为Art这个伟大答案的包装,并使用OP最初要求的语法,这里有一个jQuery扩展,它可以记录在集合元素之外是否发生了单击。
$.fn.clickOutsideThisElement = function (callback) {
return this.each(function () {
var self = this;
$(document).click(function (e) {
if (!$(e.target).closest(self).length) {
callback.call(self, e)
}
})
});
};
然后你可以这样打电话:
$("#menuscontainer").clickOutsideThisElement(function() {
// handle menu toggle
});
这是小提琴演示
首先,您必须使用mouseenter和mouseleave事件跟踪鼠标是在元素1内部还是外部。然后,您可以创建一个覆盖整个屏幕的element2来检测任何点击,并根据您是在element1内部还是外部做出相应的反应。
我强烈建议同时处理初始化和清理,出于明显的原因,element2应尽可能临时。
在下面的示例中,覆盖是位于某个位置的元素,可以通过单击内部来选择,也可以通过单击外部来取消选择。_init和_release方法是自动初始化/清理过程的一部分。该类继承自具有内部和外部元素的ClickOverlay,不用担心。我使用了outerElement.parentNode.appendChild来避免冲突。
import ClickOverlay from './ClickOverlay.js'
/* CSS */
// .unselect-helper {
// position: fixed; left: -100vw; top: -100vh;
// width: 200vw; height: 200vh;
// }
// .selected {outline: 1px solid black}
export default class ResizeOverlay extends ClickOverlay {
_init(_opts) {
this.enterListener = () => this.onEnter()
this.innerElement.addEventListener('mouseenter', this.enterListener)
this.leaveListener = () => this.onLeave()
this.innerElement.addEventListener('mouseleave', this.leaveListener)
this.selectListener = () => {
if (this.unselectHelper)
return
this.unselectHelper = document.createElement('div')
this.unselectHelper.classList.add('unselect-helper')
this.unselectListener = () => {
if (this.mouseInside)
return
this.clearUnselectHelper()
this.onUnselect()
}
this.unselectHelper.addEventListener('pointerdown'
, this.unselectListener)
this.outerElement.parentNode.appendChild(this.unselectHelper)
this.onSelect()
}
this.innerElement.addEventListener('pointerup', this.selectListener)
}
_release() {
this.innerElement.removeEventListener('mouseenter', this.enterListener)
this.innerElement.removeEventListener('mouseleave', this.leaveListener)
this.innerElement.removeEventListener('pointerup', this.selectListener)
this.clearUnselectHelper()
}
clearUnselectHelper() {
if (!this.unselectHelper)
return
this.unselectHelper.removeEventListener('pointerdown'
, this.unselectListener)
this.unselectHelper.remove()
delete this.unselectListener
delete this.unselectHelper
}
onEnter() {
this.mouseInside = true
}
onLeave() {
delete this.mouseInside
}
onSelect() {
this.innerElement.classList.add('selected')
}
onUnselect() {
this.innerElement.classList.remove('selected')
}
}
所有这些答案都解决了这个问题,但我想提供一个完全符合需要的moders es6解决方案。我只是希望让大家对这个可运行的演示感到满意。
window.clickOutSide=(元素,单击外部,单击内部)=>{document.addEventListener('click',(event)=>{if(!element.contains(event.target)){if(单击类型内部==“函数”){单击外侧();}}其他{if(单击类型内部==“函数”){单击内侧();}}});};window.clickOutSide(document.querySelector('.block'),()=>警报('clicked outside');.块{宽度:400px;高度:400px;背景色:红色;}<div class=“block”></div>