Dropdown focusout 无法按预期处理后代元素
Dropdown focusout not working on descendant elements as expected
我知道这是一个常见问题,但我看到的 none 答案解决了我的问题,如果我遗漏了一个答案,我深表歉意,这显然是 removed/marked 重复的。 ..
标记
<div class="has-dropdown">
<button class="js-dropdown-trigger">
Dropdown
</button>
<div class="dropdown">
<div class="dropdown__item">
Some random text with a <a href="#" class="stop-propagation">link</a> in it.
</div>
<div class="dropdown__divider"></div>
<div class="dropdown__item">
<a href="#">Item One</a>
</div>
<div class="dropdown__item">
<a href="#">Item Two</a>
</div>
<div class="dropdown__item">
<a href="#">Item Three</a>
</div>
</div>
</div>
脚本
$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
const $dropdown = $(event.currentTarget).next('.dropdown');
if (!$dropdown.hasClass('is-active')) {
$dropdown.addClass('is-active');
} else {
$dropdown.removeClass('is-active');
}
});
$('.has-dropdown').on('focusout', (event) => {
const $dropdown = $(event.currentTarget).children('.dropdown');
$dropdown.removeClass('is-active');
});
造型
.has-dropdown {
display: inline-flex;
position: relative;
}
.dropdown {
background-color: #eee;
border: 1px solid #999;
display: none;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
width: 300px;
margin-top: 5px;
}
.dropdown.is-active {
display: flex;
}
.dropdown__item {
padding: 10px;
}
.dropdown__divider {
border-bottom: 1px solid #999;
}
Fiddle
http://jsfiddle.net/joemottershaw/3yzadmek/
非常简单,单击 js-dropdown-trigger
切换 is-active
下拉菜单 class 很好,单击 has-dropdown
容器外部会删除 is-active
下拉菜单 class也是。
除此之外,我期望发生的事情是关注 has-dropdown
元素的后代元素(单击或制表符),这意味着 focusout
事件处理程序不应像您一样被触发仍然关注 has-dropdown
容器的后代元素。
The focusout
event is sent to an element when it, or any element
inside of it, loses focus. This is distinct from the blur event in
that it supports detecting the loss of focus on descendant elements
我知道我可以删除 focusout
事件处理程序并使用类似的东西:
$(document).on('click', (event) =>{
const $dropdownContainer = $('.has-dropdown');
if (!$dropdownContainer.is(event.target) && $dropdownContainer.has(event.target).length === 0) {
$dropdownContainer.find('.dropdown').removeClass('is-active');
}
});
这有效,但如果您单击触发器然后通过 links 切换,当您通过最后一个 link 切换时,下拉菜单仍然可见。只是在努力寻找最好的解决方案来保持事物的可访问性。
如果可能的话,我想坚持focusout
方法。
根据 回答更新
虽然更新后的脚本适用于单个元素,但将其他元素添加到 body
会导致 focusout
不再按预期工作。我认为这是因为即使将焦点应用于 has-dropdown
容器之后的任何元素,而不仅仅是后代,if
语句似乎也是正确的?因为如果您要更新 HTML 并添加更多可聚焦的元素,例如在下拉列表之后的输入。当从 has-dropdown
容器中的最后一个可聚焦元素跳转到输入时,下拉列表保持活动状态。它仅在下拉列表是 DOM 中的最后一个元素时才有效,并且仅在焦点完全丢失在 DOM 上时触发。
您的代码就快完成了——但我认为还需要更清楚地说明 focusout
如何处理原生不可聚焦的元素(即:div、p ) 及其后代,它们可以是可聚焦元素(输入、锚点)。
当容器绑定到包含可聚焦元素的 focusout
事件时,它的 focusout
事件被触发 每次 它的任何可聚焦子元素失去焦点 - 这可能是通过键盘导航或通过单击另一个子元素或容器本身。我设置了一个 fiddle 来演示:https://jsfiddle.net/darshanags/v5gk2cz8/ - 每次容器获得或失去焦点时都会发送控制台消息。
问:那么问题中给出的例子中,为什么菜单隐藏了自己?
A: 当你点击按钮时,菜单会变得可见,点击按钮会使按钮获得焦点。但是,一旦您单击菜单或菜单元素内的锚点,按钮就会失去焦点 - 这反过来会触发父元素的 'focusout' 事件并导致菜单隐藏自身。发生这种情况是因为 focusout
事件支持事件冒泡。
问:我们如何解决这个问题?
A1.1: 我们通过给它一个 tabindex 使父元素可聚焦:
<div class="has-dropdown" tabindex="0">
这解决了几件重要的事情:
- 为
div
元素提供焦点轮廓 - 这反过来增强了元素的可访问性。
- 帮助点击事件在
div
元素上注册与焦点相关的事件 - 在本例中,这将是 focusout
。如果 tabindex
被省略,我们将需要添加额外的 JavaScript 来补偿丢失的功能。
A1.2:在 focusout
事件处理程序中,我们检查获得焦点的元素是否是父元素的后代,如果是,我们不删除is-active
class 从菜单中。
完整示例代码:
$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
const $dropdown = $(event.currentTarget).next('.dropdown');
if (!$dropdown.hasClass('is-active')) {
$dropdown.addClass('is-active');
} else {
$dropdown.removeClass('is-active');
}
});
$('.has-dropdown').on('focusout', function(event) {
const $reltarget = $(event.relatedTarget);
const $currenttarget = $(event.currentTarget);
const $dropdown = $currenttarget.children('.dropdown');
// remove 'is-active' class only if the element
// that is gaining focus is not a child of the parent.
// parent = div.has-dropdown
if (!$reltarget.closest('.has-dropdown').is($currenttarget)) {
$dropdown.removeClass('is-active');
}
});
.has-dropdown {
display: inline-flex;
position: relative;
}
.dropdown {
background-color: #eee;
border: 1px solid #999;
display: none;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
width: 300px;
margin-top: 5px;
}
.dropdown.is-active {
display: flex;
}
.dropdown__item {
padding: 10px;
}
.dropdown__divider {
border-bottom: 1px solid #999;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="has-dropdown" tabindex="0">
<button class="js-dropdown-trigger">
Dropdown
</button>
<div class="dropdown">
<div class="dropdown__item">
Some random text with a <a href="#" class="stop-propagation">link</a> in it.
</div>
<div class="dropdown__divider"></div>
<div class="dropdown__item">
<a href="#">Item One</a>
</div>
<div class="dropdown__item">
<a href="#">Item Two</a>
</div>
<div class="dropdown__item">
<a href="#">Item Three</a>
</div>
</div>
</div>
<div class="has-dropdown" tabindex="0">
<button class="js-dropdown-trigger">
Dropdown
</button>
<div class="dropdown">
<div class="dropdown__item">
Some random text with a <a href="#" class="stop-propagation">link</a> in it.
</div>
<div class="dropdown__divider"></div>
<div class="dropdown__item">
<a href="#">Item One</a>
</div>
<div class="dropdown__item">
<a href="#">Item Two</a>
</div>
<div class="dropdown__item">
<a href="#">Item Three</a>
</div>
</div>
</div>
<input name="tf" type="text"/>
我已经分叉了原来的 fiddle 并对其进行了修改以展示它的实际效果。您可以在此处找到修改后的 fiddle:http://jsfiddle.net/darshanags/60jnusvk/.
其他有用信息:
我使用 event.relatedTarget
来确定每次触发 focusout
事件时获得焦点的元素。有关 event.relatedTarget
的更多信息可在此处找到:https://api.jquery.com/event.relatedTarget/.
更新
我重构了一些原始代码,现在更简单了:http://jsfiddle.net/darshanags/60jnusvk/24/。我会留下这个 fiddle 和原文供参考。
我知道这是一个常见问题,但我看到的 none 答案解决了我的问题,如果我遗漏了一个答案,我深表歉意,这显然是 removed/marked 重复的。 ..
标记
<div class="has-dropdown">
<button class="js-dropdown-trigger">
Dropdown
</button>
<div class="dropdown">
<div class="dropdown__item">
Some random text with a <a href="#" class="stop-propagation">link</a> in it.
</div>
<div class="dropdown__divider"></div>
<div class="dropdown__item">
<a href="#">Item One</a>
</div>
<div class="dropdown__item">
<a href="#">Item Two</a>
</div>
<div class="dropdown__item">
<a href="#">Item Three</a>
</div>
</div>
</div>
脚本
$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
const $dropdown = $(event.currentTarget).next('.dropdown');
if (!$dropdown.hasClass('is-active')) {
$dropdown.addClass('is-active');
} else {
$dropdown.removeClass('is-active');
}
});
$('.has-dropdown').on('focusout', (event) => {
const $dropdown = $(event.currentTarget).children('.dropdown');
$dropdown.removeClass('is-active');
});
造型
.has-dropdown {
display: inline-flex;
position: relative;
}
.dropdown {
background-color: #eee;
border: 1px solid #999;
display: none;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
width: 300px;
margin-top: 5px;
}
.dropdown.is-active {
display: flex;
}
.dropdown__item {
padding: 10px;
}
.dropdown__divider {
border-bottom: 1px solid #999;
}
Fiddle
http://jsfiddle.net/joemottershaw/3yzadmek/
非常简单,单击 js-dropdown-trigger
切换 is-active
下拉菜单 class 很好,单击 has-dropdown
容器外部会删除 is-active
下拉菜单 class也是。
除此之外,我期望发生的事情是关注 has-dropdown
元素的后代元素(单击或制表符),这意味着 focusout
事件处理程序不应像您一样被触发仍然关注 has-dropdown
容器的后代元素。
The
focusout
event is sent to an element when it, or any element inside of it, loses focus. This is distinct from the blur event in that it supports detecting the loss of focus on descendant elements
我知道我可以删除 focusout
事件处理程序并使用类似的东西:
$(document).on('click', (event) =>{
const $dropdownContainer = $('.has-dropdown');
if (!$dropdownContainer.is(event.target) && $dropdownContainer.has(event.target).length === 0) {
$dropdownContainer.find('.dropdown').removeClass('is-active');
}
});
这有效,但如果您单击触发器然后通过 links 切换,当您通过最后一个 link 切换时,下拉菜单仍然可见。只是在努力寻找最好的解决方案来保持事物的可访问性。
如果可能的话,我想坚持focusout
方法。
根据
虽然更新后的脚本适用于单个元素,但将其他元素添加到 body
会导致 focusout
不再按预期工作。我认为这是因为即使将焦点应用于 has-dropdown
容器之后的任何元素,而不仅仅是后代,if
语句似乎也是正确的?因为如果您要更新 HTML 并添加更多可聚焦的元素,例如在下拉列表之后的输入。当从 has-dropdown
容器中的最后一个可聚焦元素跳转到输入时,下拉列表保持活动状态。它仅在下拉列表是 DOM 中的最后一个元素时才有效,并且仅在焦点完全丢失在 DOM 上时触发。
您的代码就快完成了——但我认为还需要更清楚地说明 focusout
如何处理原生不可聚焦的元素(即:div、p ) 及其后代,它们可以是可聚焦元素(输入、锚点)。
当容器绑定到包含可聚焦元素的 focusout
事件时,它的 focusout
事件被触发 每次 它的任何可聚焦子元素失去焦点 - 这可能是通过键盘导航或通过单击另一个子元素或容器本身。我设置了一个 fiddle 来演示:https://jsfiddle.net/darshanags/v5gk2cz8/ - 每次容器获得或失去焦点时都会发送控制台消息。
问:那么问题中给出的例子中,为什么菜单隐藏了自己?
A: 当你点击按钮时,菜单会变得可见,点击按钮会使按钮获得焦点。但是,一旦您单击菜单或菜单元素内的锚点,按钮就会失去焦点 - 这反过来会触发父元素的 'focusout' 事件并导致菜单隐藏自身。发生这种情况是因为 focusout
事件支持事件冒泡。
问:我们如何解决这个问题?
A1.1: 我们通过给它一个 tabindex 使父元素可聚焦:
<div class="has-dropdown" tabindex="0">
这解决了几件重要的事情:
- 为
div
元素提供焦点轮廓 - 这反过来增强了元素的可访问性。 - 帮助点击事件在
div
元素上注册与焦点相关的事件 - 在本例中,这将是focusout
。如果tabindex
被省略,我们将需要添加额外的 JavaScript 来补偿丢失的功能。
A1.2:在 focusout
事件处理程序中,我们检查获得焦点的元素是否是父元素的后代,如果是,我们不删除is-active
class 从菜单中。
完整示例代码:
$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
const $dropdown = $(event.currentTarget).next('.dropdown');
if (!$dropdown.hasClass('is-active')) {
$dropdown.addClass('is-active');
} else {
$dropdown.removeClass('is-active');
}
});
$('.has-dropdown').on('focusout', function(event) {
const $reltarget = $(event.relatedTarget);
const $currenttarget = $(event.currentTarget);
const $dropdown = $currenttarget.children('.dropdown');
// remove 'is-active' class only if the element
// that is gaining focus is not a child of the parent.
// parent = div.has-dropdown
if (!$reltarget.closest('.has-dropdown').is($currenttarget)) {
$dropdown.removeClass('is-active');
}
});
.has-dropdown {
display: inline-flex;
position: relative;
}
.dropdown {
background-color: #eee;
border: 1px solid #999;
display: none;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
width: 300px;
margin-top: 5px;
}
.dropdown.is-active {
display: flex;
}
.dropdown__item {
padding: 10px;
}
.dropdown__divider {
border-bottom: 1px solid #999;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="has-dropdown" tabindex="0">
<button class="js-dropdown-trigger">
Dropdown
</button>
<div class="dropdown">
<div class="dropdown__item">
Some random text with a <a href="#" class="stop-propagation">link</a> in it.
</div>
<div class="dropdown__divider"></div>
<div class="dropdown__item">
<a href="#">Item One</a>
</div>
<div class="dropdown__item">
<a href="#">Item Two</a>
</div>
<div class="dropdown__item">
<a href="#">Item Three</a>
</div>
</div>
</div>
<div class="has-dropdown" tabindex="0">
<button class="js-dropdown-trigger">
Dropdown
</button>
<div class="dropdown">
<div class="dropdown__item">
Some random text with a <a href="#" class="stop-propagation">link</a> in it.
</div>
<div class="dropdown__divider"></div>
<div class="dropdown__item">
<a href="#">Item One</a>
</div>
<div class="dropdown__item">
<a href="#">Item Two</a>
</div>
<div class="dropdown__item">
<a href="#">Item Three</a>
</div>
</div>
</div>
<input name="tf" type="text"/>
我已经分叉了原来的 fiddle 并对其进行了修改以展示它的实际效果。您可以在此处找到修改后的 fiddle:http://jsfiddle.net/darshanags/60jnusvk/.
其他有用信息:
我使用 event.relatedTarget
来确定每次触发 focusout
事件时获得焦点的元素。有关 event.relatedTarget
的更多信息可在此处找到:https://api.jquery.com/event.relatedTarget/.
更新
我重构了一些原始代码,现在更简单了:http://jsfiddle.net/darshanags/60jnusvk/24/。我会留下这个 fiddle 和原文供参考。