我有一些HTML菜单,当用户单击这些菜单的头部时,我会完全显示这些菜单。当用户在菜单区域外单击时,我希望隐藏这些元素。

jQuery是否可以实现这样的功能?

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

当前回答

对于那些希望将一个简短的解决方案集成到JS代码中的人来说——一个没有JQuery的小型库:

用法:

// demo code
var htmlElem = document.getElementById('my-element')
function doSomething(){ console.log('outside click') }

// use the lib
var removeListener = new elemOutsideClickListener(htmlElem, doSomething);

// deregister on your wished event
$scope.$on('$destroy', removeListener);

下面是库:


function elemOutsideClickListener (element, outsideClickFunc, insideClickFunc) {
   function onClickOutside (e) {
      var targetEl = e.target; // clicked element
      do {
         // click inside
         if (targetEl === element) {
            if (insideClickFunc) insideClickFunc();
            return;

         // Go up the DOM
         } else {
            targetEl = targetEl.parentNode;
         }
      } while (targetEl);

      // click outside
      if (!targetEl && outsideClickFunc) outsideClickFunc();
   }

   window.addEventListener('click', onClickOutside);

   return function () {
      window.removeEventListener('click', onClickOutside);
   };
}

我从这里获取代码并将其放在函数中:https://www.w3docs.com/snippets/javascript/how-to-detect-a-click-outside-an-element.html

其他回答

这里的其他解决方案不适合我,所以我不得不使用:

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);

用纯JavaScript编写的方法

let menu = document.getElementById("menu");

document.addEventListener("click", function(){
    // Hide the menus
    menu.style.display = "none";
}, false);

document.getElementById("menuscontainer").addEventListener("click", function(e){
    // Show the menus
    menu.style.display = "block";
    e.stopPropagation();
}, false);

您可以监听文档上的单击事件,然后使用.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).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");

只是一个警告,使用这个:

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

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

它阻止RubyonRails UJS驱动程序正常工作。例如,link_to“click”、“/url”、:method=>:delete将不起作用。

这可能是一种变通方法:

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

$('#menucontainer').click(function(event){
  if (!$(event.target).data('method')) {
    event.stopPropagation();
  }
});