我有一些HTML菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外单击时,我希望隐藏这些元素。
jQuery是否可以实现这样的功能?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
我有一些HTML菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外单击时,我希望隐藏这些元素。
jQuery是否可以实现这样的功能?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
当前回答
这里的其他解决方案不适合我,所以我不得不使用:
if(!$(event.target).is('#foo'))
{
// hide menu
}
编辑:普通Javascript变体(2021-03-31)
我使用这个方法来处理在单击下拉菜单外部时关闭下拉菜单的问题。
首先,我为组件的所有元素创建了一个自定义类名。这个类名将被添加到组成菜单小部件的所有元素中。
const className = `dropdown-${Date.now()}-${Math.random() * 100}`;
我创建了一个函数来检查单击和单击元素的类名。如果clicked元素不包含我上面生成的自定义类名,它应该将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);
其他回答
首先,您必须使用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')
}
}
标记为接受答案的答案没有考虑到元素上可以有覆盖,如对话框、弹出窗口、日期选择器等。单击这些按钮不应隐藏元素。
我制作了自己的版本,确实考虑到了这一点。它是作为KnockoutJS绑定创建的,但它可以很容易地仅转换为jQuery。
它通过第一次查询所有具有z索引或绝对位置的可见元素来工作。然后,如果在外部单击,它会根据我想要隐藏的元素来测试这些元素。如果是命中,我会计算一个新的边界矩形,该矩形考虑到覆盖边界。
ko.bindingHandlers.clickedIn = (function () {
function getBounds(element) {
var pos = element.offset();
return {
x: pos.left,
x2: pos.left + element.outerWidth(),
y: pos.top,
y2: pos.top + element.outerHeight()
};
}
function hitTest(o, l) {
function getOffset(o) {
for (var r = { l: o.offsetLeft, t: o.offsetTop, r: o.offsetWidth, b: o.offsetHeight };
o = o.offsetParent; r.l += o.offsetLeft, r.t += o.offsetTop);
return r.r += r.l, r.b += r.t, r;
}
for (var b, s, r = [], a = getOffset(o), j = isNaN(l.length), i = (j ? l = [l] : l).length; i;
b = getOffset(l[--i]), (a.l == b.l || (a.l > b.l ? a.l <= b.r : b.l <= a.r))
&& (a.t == b.t || (a.t > b.t ? a.t <= b.b : b.t <= a.b)) && (r[r.length] = l[i]));
return j ? !!r.length : r;
}
return {
init: function (element, valueAccessor) {
var target = valueAccessor();
$(document).click(function (e) {
if (element._clickedInElementShowing === false && target()) {
var $element = $(element);
var bounds = getBounds($element);
var possibleOverlays = $("[style*=z-index],[style*=absolute]").not(":hidden");
$.each(possibleOverlays, function () {
if (hitTest(element, this)) {
var b = getBounds($(this));
bounds.x = Math.min(bounds.x, b.x);
bounds.x2 = Math.max(bounds.x2, b.x2);
bounds.y = Math.min(bounds.y, b.y);
bounds.y2 = Math.max(bounds.y2, b.y2);
}
});
if (e.clientX < bounds.x || e.clientX > bounds.x2 ||
e.clientY < bounds.y || e.clientY > bounds.y2) {
target(false);
}
}
element._clickedInElementShowing = false;
});
$(element).click(function (e) {
e.stopPropagation();
});
},
update: function (element, valueAccessor) {
var showing = ko.utils.unwrapObservable(valueAccessor());
if (showing) {
element._clickedInElementShowing = true;
}
}
};
})();
对于iPad和iPhone等触摸设备,我们可以使用以下代码:
$(document).on('touchstart', function (event) {
var container = $("YOUR CONTAINER SELECTOR");
if (!container.is(e.target) && // If the target of the click isn't the container...
container.has(e.target).length === 0) // ... nor a descendant of the container
{
container.hide();
}
});
我在YUI 3中是这样做的:
// Detect the click anywhere other than the overlay element to close it.
Y.one(document).on('click', function (e) {
if (e.target.ancestor('#overlay') === null && e.target.get('id') != 'show' && overlay.get('visible') == true) {
overlay.hide();
}
});
我正在检查祖先是否不是小部件元素容器,如果目标不是打开控件/元素的对象,如果我想关闭的小部件/元素已经打开(不那么重要)。
正如另一位海报所说,有很多陷阱,特别是如果你正在显示的元素(在本例中是菜单)具有交互元素。我发现以下方法相当稳健:
$('#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);
}
})
});