如何检测元素外部的点击?

时间:2020-03-06 14:55:17  来源:igfitidea点击:

我有一些HTML菜单,当用户单击这些菜单的标题时,它们会完整显示。当用户在菜单区域之外单击时,我想隐藏这些元素。

jQuery可能会发生这种情况吗?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});

解决方案

检查窗口单击事件目标(只要没有在其他任何地方捕获,它应该传播到窗口),并确保它不是任何菜单元素。如果不是,那么我们就在菜单之外。

或者检查单击的位置,然后查看它是否包含在菜单区域中。

NOTE: Using stopEventPropagation() is something that should be avoided as it breaks normal event flow in the DOM. See this article for more information. Consider using this method instead.

将单击事件添加到关闭窗口的文档主体。将单独的click事件添加到容器,以停止传播到文档主体。

$(window).click(function() {
//Hide the menus if visible
});

$('#menucontainer').click(function(event){
    event.stopPropagation();
});

我有一个与Eran示例类似的应用程序,除了在打开菜单时将click事件添加到主体上。

$('#menucontainer').click(function(event) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

有关jQueryone()函数的更多信息

这里的其他解决方案对我不起作用,所以我不得不使用:

if(!$(event.target).is('#foo'))
{
    // hide menu
}

如果我们正在为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;}

$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

对我有用。

现在有一个用于此的插件:外部事件(博客文章)

将clickoutside处理程序(WLOG)绑定到元素时,将发生以下情况:

  • 将该元素添加到一个数组,该数组包含带有clickoutside处理程序的所有元素
  • (命名空间的)单击处理程序已绑定到文档(如果尚不存在)
  • 在文档中的任何单击上,都会为该数组中与click-events目标不相等或者不是其父元素的那些元素触发clickoutside事件
  • 此外,clickoutside事件的event.target设置为用户单击的元素(因此,我们甚至知道用户单击了什么,而不仅仅是他在外部单击了)

因此,不会停止任何事件的传播,并且可以在外部处理程序的"上方"使用其他单击处理程序。

这对我来说非常正常:

$('body').click(function() {
    // Hide the menus if visible.
});

我们可以使用.closest()来监听document上的click事件,然后确保#menucontainer不是被单击元素的祖先或者目标。

如果不是,则单击的元素在#menucontainer之外,我们可以安全地将其隐藏。

$(document).click(function(event) { 
    if(!$(event.target).closest('#menucontainer').length) {
        if($('#menucontainer').is(":visible")) {
            $('#menucontainer').hide();
        }
    }        
});

编辑2017-06-23

如果我们打算关闭菜单并想停止监听事件,则还可以在事件监听器之后进行清理。此功能将仅清理新创建的侦听器,并在document上保留所有其他单击侦听器。使用ES2015语法:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    if (!$(event.target).closest(selector).length) {
      if ($(selector).is(':visible')) {
        $(selector).hide()
        removeClickListener()
      }
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener)
  }

  document.addEventListener('click', outsideClickListener)
}

编辑2018-03-11

对于那些不想使用jQuery的人。这是上面的纯香草代码(ECMAScript6)中的代码。

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target)) { // or use: event.target.closest(selector) === null
            if (isVisible(element)) {
                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:https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

功能:

$(function() {
    $.fn.click_inout = function(clickin_handler, clickout_handler) {
        var item = this;
        var is_me = false;
        item.click(function(event) {
            clickin_handler(event);
            is_me = true;
        });
        $(document).click(function(event) {
            if (is_me) {
                is_me = false;
            } else {
                clickout_handler(event);
            }
        });
        return this;
    }
});

用法:

this.input = $('<input>')
    .click_inout(
        function(event) { me.ShowTree(event); },
        function() { me.Hide(); }
    )
    .appendTo(this.node);

而且功能很简单:

ShowTree: function(event) {
    this.data_span.show();
}
Hide: function() {
    this.data_span.hide();
}

我在以下方面取得了成功:

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"的主体。