2

我知道这是一个常见问题,但我所见过的答案都没有解决我的问题,如果我错过了一个答案,我深表歉意,这显然可以被删除/标记为重复......

标记

<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;
}

小提琴

http://jsfiddle.net/joemottershaw/3yzadmek/

它非常简单,单击js-dropdown-trigger切换下拉类很好,单击容器is-active外部也会删除下拉类。has-dropdownis-active

除了,我期望发生的是关注元素的后代元素(单击或选项卡),has-dropdown这意味着focusout不应触发事件处理程序,因为您仍然专注于has-dropdown容器的后代元素。

当某个元素或其中的任何元素失去焦点时,该focusout事件将被发送到该元素。这与 blur 事件不同,它支持检测对后代元素的焦点丢失

我知道我可以删除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');
    }
});

这可行,但是如果您要单击触发器,然后通过选项卡浏览链接,则当您通过最后一个链接进行选项卡时,下拉菜单仍然可见。只是努力寻找最佳解决方案来保持事物的可访问性。

如果可能的话,我想坚持这种focusout方法。

根据darshanags答案更新

尽管更新后的脚本适用于单个元素,但添加其他元素body会导致focusout不再按预期工作。我认为这是因为即使将焦点应用于容器之后的任何元素,而不仅仅是后代,该if语句似乎也是正确的?has-dropdown因为如果您要更新 HTML 并在下拉菜单后添加更多可聚焦元素,例如输入。当从has-dropdown容器内的最后一个可聚焦元素切换到输入时,下拉菜单保持活动状态。它仅在下拉列表是 DOM 中的最后一个元素时才有效,并且仅在焦点完全失去 DOM 时触发。

4

1 回答 1

2

您的代码几乎就在那里 - 但我认为需要更清楚地了解如何focusout处理本机不可聚焦的元素(即:div、p)及其可能是可聚焦元素的后代(输入、锚点) .

当容器绑定到focusout包含可聚焦元素的事件时,每次focusout它的任何可聚焦子元素失去焦点时都会触发该事件- 这可能是通过键盘导航或单击另一个子元素或容器本身。我设置了一个小提琴来证明这一点:https ://jsfiddle.net/darshanags/v5gk2cz8/ - 每次容器获得或失去焦点时都会发送控制台消息。

问:那么,为什么菜单在问题中给出的示例中隐藏了自己?

A:当您单击按钮时菜单变为可见,单击按钮使按钮获得焦点本身。但是,一旦您单击菜单或菜单元素内的锚点,按钮就会失去焦点 - 这反过来会触发父元素的 'focusout' 事件并导致菜单隐藏自身。这是因为focusout事件支持事件冒泡。

问:我们如何解决这个问题?

A1.1:我们通过给它一个 tabindex 来使父元素具有焦点:

<div class="has-dropdown" tabindex="0">

这解决了几个重要的问题:

  1. div元素一个焦点轮廓——这反过来又增强了元素的可访问性。
  2. 帮助单击事件在div元素上注册与焦点相关的事件 - 在此示例中,将focusout. 如果tabindex省略了,我们需要添加额外的 JavaScript 来弥补丢失的功能。

A1.2:在focusout事件处理程序中,我们检查获得焦点的元素是否是父元素的后代,如果是,我们不会is-active从菜单中删除该类。

完整的示例代码:

$('.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"/>

我已经对原始小提琴进行了分叉并对其进行了修改以显示这一点。您可以在这里找到修改后的小提琴:http: //jsfiddle.net/darshanags/60jnusvk/

其他有用信息:

event.relatedTarget用来确定每次focusout触发事件时获得焦点的元素。更多信息event.relatedTarget可以在这里找到:https ://api.jquery.com/event.relatedTarget/ 。

更新

我已经重构了一些原始代码,现在更简单了:http: //jsfiddle.net/darshanags/60jnusvk/24/。我将留下这个小提琴和原件以供参考。

于 2018-08-13T11:35:54.557 回答