关于javascript:如何检测元素外部的单击?

How do I detect a click outside an element?

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

jquery是否可以这样做?

1
2
3
$("#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

将单击事件附加到关闭窗口的文档正文。将一个单独的单击事件附加到容器,该容器将停止向文档正文传播。

1
2
3
4
5
6
7
$(window).click(function() {
//Hide the menus if visible
});

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


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

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

1
2
3
4
5
6
7
$(document).click(function(event) {
  $target = $(event.target);
  if(!$target.closest('#menucontainer').length &&
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

编辑:2017-06-23

如果计划关闭菜单并希望停止侦听事件,也可以在事件侦听器之后进行清理。此函数将只清理新创建的侦听器,保留document上的任何其他单击侦听器。使用ES2015语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的用户。上面的代码是普通的vanillajs(ecmascript6)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 comment,只使用!element.contains(event.target)而不是jquery部分。

但是现在所有主要的浏览器都可以使用element.closest()(w3c版本与jquery版本稍有不同)。polyfill可以在这里找到:https://developer.mozilla.org/en-us/docs/web/api/element/closest


How to detect a click outside an element?

原因,这个问题是如此的流行,有这么多的答案是,它是deceptively复杂。八年后,几乎和dozens奇怪的答案,我是真正的看到如何护理已被给定的accessibility大小。

I would like to hide these elements when the user clicks outside the menus' area.

这是一个崇高的事业,是实际的问题。标题的问题与答案的表示,这是他最大的attempt出现大的地址与您的安全unfortunate包含Red Herring。

hint的话:这是"点击"!

你不真的想要handlers bind点击。

如果你是大的dialog handlers结合点击关闭,你已经已经失败。你已经失败的原因是,并非每个triggers click事件。用户不使用一个大鼠能将你的dialog Escape(和你的arguably流行起来的菜单是A型dialog)由压tab,和然后他们不会是能读的内容背后的一个dialog无subsequently triggering click事件。

所以让我们rephrase这个问题。

How does one close a dialog when a user is finished with it?

这是目标。不幸的是,现在我们需要的userisfinishedwiththedialogbind结合事件,那不是straightforward相比。

所以,我们如何可以detect用户使用一个dialog已完成?

focusout事件

一个很好的开始,如果焦点是确定的dialog已经离开了。

小心:是blurhint的事件,如果不blurpropagate的事件是bubbling绑定的阶段!

jquery focusout会做的只是罚款。如果你不能使用,你可以使用jquery,然后在blur地捕获阶段:

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

同时,对于许多对话框允许你会需要大的容器获得焦点。添加到允许的dialog tabindex="-1"大无焦点的receive dynamically否则interrupting tabbing流。

1
2
3
4
5
6
7
$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
1
2
3
4
5
6
div {
  display: none;
}
.active {
  display: block;
}
1
2
3
4
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
Example

  Lorem ipsum dolor sit amet.

如果你的游戏与演示为超过一分钟,你应该很快开始看到的问题。

第一个是,该链接在dialog不是clickable。在试图点击它或它会导致大的标签的dialog closing之前的相互作用需要的地方。这是因为一个关于元素的内在triggers focusout事件之前triggering一focusin事件再次。

是queue修复的状态变化对该事件的环。这不能做为setImmediate(...)由使用,或setTimeout(..., 0)browsers setImmediate,不支持。它可以一次queued cancelled随后由一个focusin

1
2
3
4
5
6
7
8
9
10
$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$('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'));
  }
});
1
2
3
4
5
6
div {
  display: none;
}
.active {
  display: block;
}
1
2
3
4
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
Example

  Lorem ipsum dolor sit amet.

第二期是不会关闭的dialog当的链接是pressed再次。这是因为dialog失去焦点,triggering关闭后的行为,其中的链接点击triggers dialog reopen的大。

类似的前一期的焦点,成为管理国家的需要。给定的状态变化,已经被queued,它只是一个重要的焦点事件的处理:对dialog triggers

<字幕>这个要看<字幕> /熟悉

1
2
3
4
5
6
7
8
9
10
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$('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'));  
  }
});
1
2
3
4
5
6
div {
  display: none;
}
.active {
  display: block;
}
1
2
3
4
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
Example

  Lorem ipsum dolor sit amet.

esc关键

如果你以为你是做焦点由美国来处理,你可以有更大的自由simplify的用户体验。

这往往是一个"很有"的特征,但它是常见的,当你有一个popup模态或任何你的关键,esc将接近它了。

1
2
3
4
5
6
keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$('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'));  
  }
});
1
2
3
4
5
6
div {
  display: none;
}
.active {
  display: block;
}
1
2
3
4
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
Example

  Lorem ipsum dolor sit amet.

如果你知道你有focusable元素内的dialog,你不会需要dialog焦点的直接测量。如果你是,你可以建立一个菜单,菜单的第一项相反的焦点。

1
2
3
4
5
6
7
click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$('.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();
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
.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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
<ul class="menu">
  <li class="menu__item">
    Menu 1
    <ul class="submenu" id="menu-1" tabindex="-1">
      <li class="submenu__item">Example 1
</li>

      <li class="submenu__item">Example 2
</li>

      <li class="submenu__item">Example 3
</li>

      <li class="submenu__item">Example 4
</li>

   
</ul>

 
</li>

  <li class="menu__item">
    Menu 2
    <ul class="submenu" id="menu-2" tabindex="-1">
      <li class="submenu__item">Example 1
</li>

      <li class="submenu__item">Example 2
</li>

      <li class="submenu__item">Example 3
</li>

      <li class="submenu__item">Example 4
</li>

   
</ul>

 
</li>


</ul>

lorem ipsum dolor sit amet.

威-咏叹调的角色和其他accessibility支持

这个回答hopefully分布式访问的键盘和鼠的基础支持这一功能,但因为它的已经相当sizable我要讨论任何avoid威-咏叹调的角色和属性,然而,我指的implementers高度recommend将军为细节的角色在什么他们应该使用和其他任何合适的属性。