我有一些HTML菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外单击时,我希望隐藏这些元素。
jQuery是否可以实现这样的功能?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
我有一些HTML菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外单击时,我希望隐藏这些元素。
jQuery是否可以实现这样的功能?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
当前回答
您可以监听文档上的单击事件,然后使用.clost()确保#menucontainer不是被单击元素的祖先或目标。
如果不是,则单击的元素位于#menucontainer之外,您可以安全地隐藏它。
$(document).click(function(event) {
var $target = $(event.target);
if(!$target.closest('#menucontainer').length &&
$('#menucontainer').is(":visible")) {
$('#menucontainer').hide();
}
});
编辑–2017-06-23
如果您计划关闭菜单并希望停止侦听事件,也可以在事件侦听器之后进行清理。此函数将只清理新创建的侦听器,保留文档上的任何其他单击侦听器。使用ES2015语法:
export function hideOnClickOutside(selector) {
const outsideClickListener = (event) => {
const $target = $(event.target);
if (!$target.closest(selector).length && $(selector).is(':visible')) {
$(selector).hide();
removeClickListener();
}
}
const removeClickListener = () => {
document.removeEventListener('click', outsideClickListener);
}
document.addEventListener('click', outsideClickListener);
}
编辑–2018-03-11
对于那些不想使用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的评论,仅供使用!元素。包含(event.target)而不是jQuery部分。
但是element.clost()现在也可以在所有主要浏览器中使用(W3C版本与jQuery版本稍有不同)。Polyfills可以在以下位置找到:Element.clost()
编辑–2020-05-21
如果您希望用户能够在元素内部单击并拖动,则在元素外部释放鼠标,而不关闭元素:
...
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);
在外部ClickListener中:
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()
}
}
其他回答
$(document).click(function() {
$(".overlay-window").hide();
});
$(".overlay-window").click(function() {
return false;
});
如果单击文档,则隐藏给定元素,除非单击同一元素。
如何检测元素外部的单击?
这个问题之所以如此流行并有如此多的答案,是因为它看起来很复杂。在经历了近八年的时间和数十个答案之后,我真的很惊讶地发现,人们对无障碍设施的关注程度如此之低。
当用户在菜单区域外单击时,我希望隐藏这些元素。
这是一项崇高的事业,也是实际问题。大多数答案似乎试图解决的问题的标题包含了一个不幸的红鲱鱼。
提示:就是“咔嚓”这个词!
您实际上不想绑定单击处理程序。
如果要绑定单击处理程序以关闭对话框,则已失败。你失败的原因是不是每个人都会触发点击事件。不使用鼠标的用户可以通过按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角色和属性的讨论,但是我强烈建议实现者参考规范,以了解他们应该使用的角色和任何其他适当属性的详细信息。
为了更易于使用和表达代码,我为此创建了一个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...
});
我知道这个问题有一百万个答案,但我一直喜欢使用HTML和CSS来完成大部分工作。在这种情况下,z索引和定位。我找到的最简单的方法如下:
$(“#show trigger”).click(function(){$(“#element”).animate({width:'toggle'});$(“#外部元素”).show();});$(“#外部元素”).click(function(){$(“#element”).hide();$(“#外部元素”).hide();});#外部元件{位置:固定;宽度:100%;高度:100%;z指数:1;显示:无;}#元素{显示:无;填充:20px;背景色:#ccc;宽度:300px;z指数:2;位置:相对;}#显示触发器{填充:20px;背景色:#ccc;边距:20px自动;z指数:2;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><div id=“outside element”></div><div id=“element”><div class=“menu item”><a href=“#1”>菜单项1</a></div><div class=“menu item”><a href=“#2”>菜单项1</a></div><div class=“menu item”><a href=“#3”>菜单项1</a></div><div class=“menu item”><a href=“#4”>菜单项1</a></div></div><div id=“show trigger”>显示菜单</div>
这创建了一个安全的环境,因为除非菜单实际打开,否则不会触发任何内容,并且z索引保护元素中的任何内容在被单击时不会产生任何错误。
此外,您不需要jQuery用传播调用覆盖所有基础,也不需要清除所有内部元素中的错误。
这是我解决问题的方法。
$(window).click(function (event) {
//To improve performance add a checklike
//if(myElement.isClosed) return;
var isClickedElementChildOfMyBox = isChildOfElement(event,'#id-of-my-element');
if (isClickedElementChildOfMyBox)
return;
//your code to hide the element
});
var isChildOfElement = function (event, selector) {
if (event.originalEvent.path) {
return event.originalEvent.path[0].closest(selector) !== null;
}
return event.originalEvent.originalTarget.closest(selector) !== null;
}