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

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

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

当前回答

$(document).on('click.menu.hide', function(e){
  if ( !$(e.target).closest('#my_menu').length ) {
    $('#my_menu').find('ul').toggleClass('active', false);
  }
});

$(document).on('click.menu.show', '#my_menu li', function(e){
  $(this).find('ul').toggleClass('active');
});
div {
  float: left;
}

ul {
  padding: 0;
  position: relative;
}
ul li {
  padding: 5px 25px 5px 10px;
  border: 1px solid silver;
  cursor: pointer;
  list-style: none;
  margin-top: -1px;
  white-space: nowrap;
}
ul li ul:before {
  margin-right: -20px;
  position: absolute;
  top: -17px;
  right: 0;
  content: "\25BC";
}
ul li ul li {
  visibility: hidden;
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0 0 1px 0;
}
ul li ul li:last-child {
  border: none;
}
ul li ul.active:before {
  content: "\25B2";
}
ul li ul.active li {
  display: list-item;
  visibility: visible;
  height: inherit;
  padding: 5px 25px 5px 10px;
}
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<div>
  <ul id="my_menu">
    <li>Menu 1
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 2
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 3</li>
    <li>Menu 4</li>
    <li>Menu 5</li>
    <li>Menu 6</li>
  </ul>
</div>

这里是jsbin版本http://jsbin.com/xopacadeni/edit?html,css,js,输出

其他回答

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

这个问题之所以如此流行并有如此多的答案,是因为它看起来很复杂。在经历了近八年的时间和数十个答案之后,我真的很惊讶地发现,人们对无障碍设施的关注程度如此之低。

当用户在菜单区域外单击时,我希望隐藏这些元素。

这是一项崇高的事业,也是实际问题。大多数答案似乎试图解决的问题的标题包含了一个不幸的红鲱鱼。

提示:就是“咔嚓”这个词!

您实际上不想绑定单击处理程序。

如果要绑定单击处理程序以关闭对话框,则已失败。你失败的原因是不是每个人都会触发点击事件。不使用鼠标的用户可以通过按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角色和属性的讨论,但是我强烈建议实现者参考规范,以了解他们应该使用的角色和任何其他适当属性的详细信息。

$(document).on('click.menu.hide', function(e){
  if ( !$(e.target).closest('#my_menu').length ) {
    $('#my_menu').find('ul').toggleClass('active', false);
  }
});

$(document).on('click.menu.show', '#my_menu li', function(e){
  $(this).find('ul').toggleClass('active');
});
div {
  float: left;
}

ul {
  padding: 0;
  position: relative;
}
ul li {
  padding: 5px 25px 5px 10px;
  border: 1px solid silver;
  cursor: pointer;
  list-style: none;
  margin-top: -1px;
  white-space: nowrap;
}
ul li ul:before {
  margin-right: -20px;
  position: absolute;
  top: -17px;
  right: 0;
  content: "\25BC";
}
ul li ul li {
  visibility: hidden;
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0 0 1px 0;
}
ul li ul li:last-child {
  border: none;
}
ul li ul.active:before {
  content: "\25B2";
}
ul li ul.active li {
  display: list-item;
  visibility: visible;
  height: inherit;
  padding: 5px 25px 5px 10px;
}
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<div>
  <ul id="my_menu">
    <li>Menu 1
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 2
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 3</li>
    <li>Menu 4</li>
    <li>Menu 5</li>
    <li>Menu 6</li>
  </ul>
</div>

这里是jsbin版本http://jsbin.com/xopacadeni/edit?html,css,js,输出

投票选出最受欢迎的答案,但添加

&& (e.target != $('html').get(0)) // ignore the scrollbar

因此,单击滚动条不会隐藏目标元素。

这是一个经典的例子,对HTML进行调整将是更好的解决方案。为什么不在不包含菜单项的元素上设置单击?那么您不需要添加传播。

$('.header, .footer, .main-content').click(function() {
//Hide the menus if visible
});

对于某些人来说,这可能是一个更好的解决方案。

$(".menu_link").click(function(){
    // show menu code
});

$(".menu_link").mouseleave(function(){
    //hide menu code, you may add a timer for 3 seconds before code to be run
});

我知道mouseleave不仅意味着在外面单击,还意味着离开该元素的区域。

一旦菜单本身位于menu_link元素内,那么单击或移动菜单本身应该不会有问题。