【问题标题】:Dropdown focusout not working on descendant elements as expected下拉焦点无法按预期对后代元素起作用
【发布时间】:2018-08-09 11:04:09
【问题描述】:

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

标记

<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-dropdown 容器外部单击也会删除is-active 下拉类。

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

focusout 事件被发送到一个元素,或任何元素 里面,失去焦点。这与中的模糊事件不同 它支持检测失去对后代元素的关注

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

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

如果可能的话,我想坚持使用focusout 方法。

根据darshanags 回答更新

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

【问题讨论】:

    标签: jquery dropdown jquery-focusout


    【解决方案1】:

    您的代码几乎已经完成了 - 但我认为需要更清楚地了解 focusout 如何处理本机不可聚焦的元素(即:div、p)及其可聚焦的后代元素(输入、锚点)。

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

    问:那么,为什么菜单在问题给出的示例中会隐藏起来?

    答:当您单击按钮时菜单变为可见,单击按钮会使按钮获得焦点本身。但是,一旦您单击菜单或菜单元素内的锚点,按钮就会失去焦点——这反过来会触发父元素的 '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/。我会把这个小提琴和原件留作参考。

    【讨论】:

    • 我不知道每次都这样调用focusout!不过,这是朝着正确方向迈出的良好一步,所以谢谢你!但是,它仅在它是正文中唯一的元素时才有效,我现在将用解释更新我的答案。
    • 我已将focusout 事件处理程序中的if 语句更改为以下内容:if (!$(event.currentTarget).has(event.relatedTarget).length) { // Hide }}。我相信这现在有效,我认为您甚至可以删除 tabindex 属性并且它有效? jsfiddle.net/joemottershaw/scnx4od9 如果你也这么认为,如果你可以用新的 if 声明更新你的答案,我会用赏金将其标记为正确!
    • @Joe,我正在查看您修改后的小提琴:jsfiddle.net/joemottershaw/scnx4od9。当我使用键盘浏览项目时,下拉菜单工作正常,但如果我单击菜单中的其他任何位置,它会关闭它 - 我认为这不是所需的行为?我正在使用火狐
    • 啊,你是对的,这也不是我们想要的行为。你明白我对你的if 语句的意思了吗?当页面上有其他元素时,保持下拉菜单处于活动状态?也许它需要两个 if 语句的组合,如果在 has-dropdown 内单击以及如果 has-dropdown 后代被聚焦,则可以使其保持打开状态?
    • 是的,你的小提琴适合我。但是我想出了一段更简单的代码,减去了我之前实现的大部分检查。新小提琴:jsfiddle.net/darshanags/60jnusvk/24
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 2017-04-12
    • 1970-01-01
    • 2022-11-18
    • 1970-01-01
    • 2017-11-11
    相关资源
    最近更新 更多