如何检测元素外部的点击?
这个问题之所以如此受欢迎,并有如此多的答案,是因为它看似复杂。经过近八年的时间和数十个答案,我真的很惊讶地发现人们对可访问性的关注如此之少。
当用户点击菜单区域之外时,我想隐藏这些元素。
这是一项崇高的事业,也是 真正的 问题。问题的标题——大多数答案似乎都试图解决这个问题——包含了一个不幸的干扰性话题。
提示:它是单词“click”!
您实际上并不想绑定点击处理程序。
如果您绑定了点击处理程序来关闭对话框,那么您已经失败了。失败的原因是,并非每个人都会触发 click
Tab退出对话框(您的弹出菜单可以说是一种对话框),然后他们将无法阅读对话框后面的内容,除非随后触发事件 click
。
因此让我们重新表述一下这个问题。
当用户完成对话后,如何关闭对话?
这就是目标。不幸的是,现在我们需要绑定事件 userisfinishedwiththedialog
,而绑定并不是那么简单。
那么我们如何检测用户已经结束使用对话框呢?
focusout
事件
一个好的开始是确定焦点是否已经离开对话框。
提示:小心处理模糊事件,如果事件绑定到冒泡阶段,模糊就不会传播!
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>
如果您玩该演示超过一分钟,您很快就会开始发现问题。
首先,对话框中的链接不可点击。尝试点击它或按 Tab 键切换到它会导致对话框在交互发生之前关闭。这是因为聚焦内部元素会触发事件, focusout
然后再触发 focusin
事件。
修复方法是将状态更改排队到事件循环中。这可以通过使用 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>
第二个问题是,再次按下链接时对话框不会关闭。这是因为对话框失去焦点,触发关闭行为,之后单击链接会触发对话框重新打开。
与上一个问题类似,需要管理焦点状态。鉴于状态更改已排队,因此只需处理对话框触发器上的焦点事件即可:
This should look familiar
$('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 角色和属性 ,但我 强烈 建议实施者参考规范以了解他们应该使用什么角色以及任何其他适当属性的详细信息。