jQuery 当后代交互元素获得焦点时,keydown() 不会触发

jQuery keydown() doesn't fire when a descendant interactive element is focused

使应用程序符合 W3C feed pattern, I must create keyboard commands that help screen reader users browse content loaded via infinite scrolling. See working example here

在示例页面上,关注列表中的一项,然后按 PAGE_DOWN/PAGE_UP。看?这使您可以浏览列表项,同时跳过每个项目的内容。

如果您将注意力集中在其中一个项目中的按钮上并尝试从那里导航,它仍会在文章之间正确导航。 这就是我希望我的应用程序的行为方式,但事实并非如此。

我的代码与示例中的代码基本相同。 <section role="feed"> 内的多个 <article> 元素。使用 jQuery,我将 'keydown' 事件附加到 <section>,称为 #product-grid

$('#product-grid').keydown(function(event) {
  // Detect which key was pressed and execute appropriate action
});

HTML结构:

<section id="product-grid" role="feed">
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>

  (...) many other <article> elements

</section>

在我的文章里面,有锚点。如果您关注它们并按下某个键,'keydown' 事件 将不会 触发。如果您将注意力集中在文章的其他任何地方,它确实如此。

据我了解,当后代项获得焦点时,父项(在本例中为 #product-grid)也获得焦点。我说得对吗?

我尝试过的事情:


这是一个 fiddle,您可以在其中重现问题: https://jsfiddle.net/fgzom4kw/

重现问题:

  1. 点击任何项目的灰色背景(不是锚点)。红色轮廓表示它已聚焦。
  2. 使用 PAGE UP 和 PAGE DOWN 浏览项目。
  3. 按 TAB 键,这会将焦点设置在其中一个锚点上。
  4. 选中锚点后,尝试使用 PAGE UP 和 PAGE DOWN 键再次导航。

将此行为与 W3C example 的行为进行比较。


问题解决有两种解决方法:

我的方式:

<matrixRef>还是高速公路!</matrixRef>

Twisty 的方式:

检查评论是否有任何警告。

我无法重现您所说的问题。也就是说,您需要一个更广泛的选择器。基本上,如果用户关注的是选择器的后代的任何项目,您需要绑定 keydown 回调。考虑以下因素。

$(function() {
  $("#product-grid").children().keydown(function(e) {
    var el = $(e.target);
    var ch = e.which;
    console.log("Event:", e.type, "HTML Target:", el.prop("nodeName"), "Char:", ch);
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section id="product-grid" role="feed">
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
  <article tabindex="-1">
    <a href="#"> having focus on this element prevents the 'keydown' event from firing
      <img src="..."/>
      <p>some text</p>
    </a>
    <div>
      if you click on this non-interactive section instead, the event fires correctly
    </div>
  </article>
</section>

使用 $("#product-grid").children() 作为选择器将抓取所有子元素。然后您可以根据需要绑定回调。

更新

根本问题是使用可选项卡元素时的当前焦点。看看新的例子:https://jsfiddle.net/Twisty/m1w2b7rv/39/

JavaScript

$(function() {
  function setFocus(i) {
    $("[aria-posinset='" + i + "']").focus();
  }

  function navFocus(c, i, a) {
    switch (c) {
      case 33: // PAGE_UP
        if (i > 1) {
          setFocus(--i);
        }
        break;
      case 34: // PAGE_DOWN
        if (i < a) {
          setFocus(++i);
        }
        break;
      case 35: // END
        if (event.ctrlKey) {
          if (i !== a) {
            setFocus(a);
          }
        }
        break;
      case 36: // HOME
        if (event.ctrlKey) {
          if (i !== 1) {
            setFocus(1);
          }
        }
        break;
    }
  }

  $("#product-grid").on("keydown", function(event) {
    var el = $(event.target);
    if (el.prop("nodeName") == "A") {
      el = el.closest("article");
      el.focus();
    }
    var itemIndex = el.attr('aria-posinset');
    var itemCount = el.attr('aria-setsize');
    navFocus(event.which, itemIndex, itemCount);
  });
});

keydown事件正在冒泡并被触发,但当前文章没有focus,所以没有很好的方法获取文章的正确索引。因此,如果焦点在 link 上,我们必须先将焦点重置回 article 元素。

我明白了。 .

答案在 W3C 示例的 JavaScript 代码中,就在这里:https://www.w3.org/TR/wai-aria-practices-1.1/examples/feed/js/feed.js

var focusedArticle =
  aria.Utils.matches(event.target, '[role="article"]') ?
    event.target :
    aria.Utils.getAncestorBySelector(event.target, '[role="article"]');

它试图找出在按键时哪个元素获得了焦点。

  1. 如果 event.target<article> 或(如示例中的)<div role="article">,它将使用该元素。
  2. 如果 event.target 不是文章,它将尝试检索它能找到的 "closest" 文章。

这意味着,如果在我们按下一个键盘命令时文章中的交互式小部件具有焦点,则将使用包含该小部件的文章。文章元素很特殊,因为它们包含在我的事件处理程序(以及屏幕阅读器)中使用的元数据(aria-posinset、aria-setsize)。诀窍在于 getAncestorBySelector 方法。

现在是这样工作的:

$('#product-grid').keydown(function(event) {
  var $focusedItem = $(event.target);

  // if focused element is not <article>, find the closest <article>
  if ($focusedItem.is(':not(article)')) {
    $focusedItem = $focusedItem.closest('article');
  }

  var itemIndex = $focusedItem.attr('aria-posinset');
  var itemCount = $focusedItem.attr('aria-setsize');

  (...)

..问题解决了:D


Fiddle 我的解决方案: https://jsfiddle.net/5sta3o82/