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

 2019-12-21 22:16

遇到的问题:

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

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

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

解决方案:

解决方案一

注意:应该避免使用stopEventPropagation() ,因为它会破坏DOM中的正常事件流。 有关更多信息,请参见本文 考虑改用此方法

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

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

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

解决方案二

您可以侦听document上的click事件,然后使用.closest()来确保#menucontainer不是其祖先,也不是clicked元素的目标。

如果不是,则单击的元素在#menucontainer外部,您可以安全地隐藏它。

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

编辑– 2017-06-23

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

 export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    $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的人。 这是上面的纯香草代码(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()

解决方案三

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

这个问题之所以如此流行并且答案如此之多,是因为它看似复杂。 经过将近八年的时间和数十个答案,我真的很惊讶地看到对可访问性的关注很少。

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

这是一个崇高的原因,也是实际问题。 问题的标题(即大多数答案似乎试图解决的问题)包含不幸的鲱鱼。

提示:这是“点击”一词!

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

如果要绑定单击处理程序以关闭对话框,则您已经失败了。 您失败的原因是,并非所有人都触发click事件。 不使用鼠标的用户将可以通过按Tab来退出对话框(并且弹出菜单可以说是对话框的一种),然后他们将无法在不随后触发click情况下读取对话框后面的内容。事件。

因此,让我们改一下这个问题。

用户完成操作后如何关闭对话框?

这是目标。 不幸的是,现在我们需要用userisfinishedwiththedialog事件绑定userisfinishedwiththedialog ,而这种绑定并不是那么简单。

那么我们如何才能检测到用户已完成使用对话框?

focusout事件

一个很好的开始是确定焦点是否已离开对话框。

提示:请注意blur事件,如果事件绑定到冒泡阶段, blur不会传播!

jQuery的focusout将很好。 如果不能使用jQuery,则可以在捕获阶段使用blur

 element.addEventListener('blur', ..., true);
//                       use capture: ^^^^ 

同样,对于许多对话框,您将需要允许容器获得焦点。 添加tabindex="-1"以允许对话框动态接收焦点,而不会中断制表流程。

 $('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> 


如果您在演示中玩了超过一分钟,则应该很快开始发现问题。

首先是对话框中的链接不可单击。 尝试单击它或它的选项卡将导致对话框在发生交互之前关闭。 这是因为聚焦内部元素会在再次触发focusout事件之前触发focusin事件。

解决方法是在状态循环上排队状态更改。 对于不支持setImmediate浏览器,可以使用setImmediate(...)setTimeout(..., 0)setImmediate 一旦排队,就可以通过随后的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

如果您认为已经通过处理焦点状态完成了操作,则可以做更多的事情来简化用户体验。

这通常是“很不错的”功能,但是通常当您使用任何形式的模式或弹出窗口时, 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角色和其他辅助功能支持

这个答案有望涵盖此功能可访问的键盘和鼠标支持的基础知识,但是由于它已经相当大了,因此我将避免任何有关WAI-ARIA角色和属性的讨论,但是我强烈建议实现者参考该规范以获取详细信息。他们应该使用什么角色以及任何其他适当的属性。

阅读 610 次发布于 2019年12月21日
推荐阅读
为什么处理排序数组要比处理未排序数组快?

这是一段C ++代码,显示了一些非常特殊的行为。 出于某些奇怪的原因,奇迹般地对数据进行排序使代码快了将近六倍: #include #include #include int main() { // Generate data const unsigned arraySize = 32768; int da...

2019-12-20 阅读 10

如何撤消Git中的最新本地提交?

我不小心将错误的文件提交给Git ,但是我还没有将提交推送到服务器。 如何撤消本地存储库中的那些提交?

2019-12-20 阅读 12

如何在本地和远程删除Git分支?

我想在本地和远程删除分支。 尝试删除远程分支失败 $ git branch -d remotes/origin/bugfix error: branch 'remotes/origin/bugfix' not found. $ git branch -d origin/bugfix error: branch 'origin/bugfix' not found. $ git branch ...

2019-12-20 阅读 9

'git pull'和'git fetch'有什么区别?

主持人注意:鉴于此问题已经发布了67个答案 (其中一些已删除),请在发布另一个问题之前考虑您是否正在贡献新内容 。 git pull和git fetch什么区别?

2019-12-20 阅读 8

什么是正确的JSON内容类型?

我一直在弄乱JSON一段时间,只是将其作为文本推出,并没有伤害任何人(据我所知),但是我想正确地做事。 我已经看到许多所谓的JSON内容类型的“标准”: application/json application/x-javascript text/javascript text/x-javascript text/x-json 但是哪一个是正确的,还是最好的? 我发现在它们之间存在安全性和浏览...

2019-12-20 阅读 10

目录