我有一些HTML菜单,当用户点击这些菜单的标题时,会完全显示它们。当用户在菜单区域外点击时,我希望隐藏这些元素。
使用jQuery是否可以实现这样的效果?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
我有一些HTML菜单,当用户点击这些菜单的标题时,会完全显示它们。当用户在菜单区域外点击时,我希望隐藏这些元素。
使用jQuery是否可以实现这样的效果?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
注意:使用
stopPropagation
应该避免,因为它会打破DOM中正常的事件流程。有关更多信息,请参见这篇CSS Tricks文章。考虑改用这种方法。
在文档主体上附加一个点击事件以关闭窗口。将另一个点击事件附加到容器,以阻止事件传播到文档主体。
$(window).click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
$('html').click()
而不是 body。body 的高度始终为其内容的高度。如果内容很少或屏幕很高,则仅在被 body 填充的部分起作用。 - meodocument
上监听一个 click 事件,然后使用 .closest()
确保被点击的元素不是 #menucontainer
的祖先或目标。
如果不是,则被点击的元素位于 #menucontainer
外部,你可以安全地隐藏它。
$(document).click(function(event) {
var $target = $(event.target);
if(!$target.closest('#menucontainer').length &&
$('#menucontainer').is(":visible")) {
$('#menucontainer').hide();
}
});
如果您计划关闭菜单并停止监听事件,您还可以在事件监听器后进行清理。该函数仅清理新创建的监听器,保留document
上任何其他点击监听器。使用 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);
}
对于那些不想使用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()
如果您希望用户能够在元素内单击并拖动鼠标,然后将其释放到元素外而不关闭该元素:
...
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()
}
}
当用户完成对话框操作后,该如何关闭对话框?
这是我们的目标。不幸的是,现在我们需要绑定userisfinishedwiththedialog
事件,而这个绑定并不那么简单。
那么,我们如何检测用户何时完成对话框操作呢?
focusout
事件一个好的起点是判断焦点是否离开了对话框。
提示:要小心blur
事件,如果事件绑定到冒泡阶段,则blur
不会传播!
jQuery的focusout
非常适合。如果您不能使用jQuery,则可以在捕获阶段使用blur
:
element.addEventListener('blur', ..., true);
// use capture: ^^^^
tabindex="-1"
可以使对话框动态地接收焦点,而不会中断 Tab 键流程。
$('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>
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'));
}
});
$('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'));
}
});
$('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();
}
}
$('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();
}
$('.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角色和属性,然而我强烈建议实施者参考规范以获取应使用的角色和任何其他适当属性的详细信息。
blur
或focusout
处理程序,您仍将完全支持鼠标和触摸用户,并且它还具有支持键盘用户的附加好处。在任何时候我都没有建议您不支持鼠标用户。 - zzzzBov其他解决方案对我无效,所以我不得不使用:
if(!$(event.target).is('#foo'))
{
// hide menu
}
我使用了这种方法来处理在单击菜单外部关闭下拉菜单的情况。
首先,我为组件的所有元素创建了一个自定义类名。这个类名将添加到组成菜单小部件的所有元素中。
const className = `dropdown-${Date.now()}-${Math.random() * 100}`;
我创建了一个函数来检查点击事件和被点击元素的类名。如果被点击的元素不包含我上面生成的自定义类名,它应该将show
标志设置为false
,并关闭菜单。const onClickOutside = (e) => {
if (!e.target.className.includes(className)) {
show = false;
}
};
然后我将点击事件处理程序附加到了窗口对象上。
// add when widget loads
window.addEventListener("click", onClickOutside);
... 最后做一些清理工作
// remove listener when destroying the widget
window.removeEventListener("click", onClickOutside);
&& !$(event.target).parents("#foo").is("#foo")
,这样任何子元素在被单击时都不会关闭菜单。 - honyovkevent.composedPath()
composedPath()
方法返回事件的路径,即将调用侦听器的对象数组。
const target = document.querySelector('#myTarget')
document.addEventListener('click', (event) => {
const withinBoundaries = event.composedPath().includes(target)
if (withinBoundaries) {
target.innerText = 'Click happened inside element'
} else {
target.innerText = 'Click happened **OUTSIDE** element'
}
})
/* Just to make it good looking. You don't need this */
#myTarget {
margin: 50px auto;
width: 500px;
height: 500px;
background: gray;
border: 10px solid black;
}
<div id="myTarget">
Click me (or not!)
</div>
if (el === event.target || el.contains(event.target))
更准确... - Saif Obeidat我有一个类似于 Eran's 示例的应用程序,不同之处在于当我打开菜单时,我将 click 事件附加到了 body... 就像这样:
$('#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>
$("#menuscontainer").click(function() {
$(this).focus();
});
$("#menuscontainer").blur(function(){
$(this).hide();
});
这对我来说完全没问题。
这对我完美地起作用了!
$('html').click(function (e) {
if (e.target.id == 'YOUR-DIV-ID') {
//do something
} else {
//do something
}
});
event.path
。https://dev59.com/XnVC5IYBdhLWcg3w4Vf6#43405204 - Dan Philip Bejoyevent.target
实现的原生JavaScript解决方案,不包含event.stopPropagation
。 - lowtechsun