【问题标题】:jQuery keydown() doesn't fire when a descendant interactive element is focused当后代交互式元素获得焦点时,jQuery keydown() 不会触发
【发布时间】:2020-04-14 06:45:24
【问题描述】:

为了使应用程序符合W3C feed pattern,我必须创建键盘命令来帮助屏幕阅读器用户浏览通过无限滚动加载的内容。请参阅工作示例 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)也处于焦点中。我说的对吗?

我尝试过的事情:

  • 委托事件,使用 on() 捕获 #product-grid 中某些元素(包括锚元素)上的 keydown 事件。
  • 直接将keydown 事件处理程序附加到&lt;article&gt; 内的锚点。当我专注于它并按下一个键时,什么也没有发生。根据我在 SO 上搜索的结果,这应该是可行的。

这是一个可以重现问题的小提琴: https://jsfiddle.net/fgzom4kw/

重现问题:

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

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


问题已解决。有两种解决方案:

我的方式:https://stackoverflow.com/a/59449938/9811172

&lt;matrixRef&gt; 或者高速公路! &lt;/matrixRef&gt;

Twisty 的方式:https://stackoverflow.com/a/59448891/9811172

检查 cmets 是否有任何警告。

【问题讨论】:

    标签: jquery jquery-events


    【解决方案1】:

    我无法重现您所说的问题。也就是说,您需要一个更广泛的选择器。基本上,如果用户关注选择器的任何后代项目,您需要绑定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,因此没有获得文章正确索引的好方法。因此,如果焦点在链接上,我们必须先将焦点重新设置回 article 元素。

    【讨论】:

    • 感谢您测试代码。不幸的是,它与另一个答案中建议的使用 find() 相同。它会导致事件在关注锚点时不触发,在关注#product-grid 的任何其他部分时也不会触发。也就是说,它使情况变得更糟。而不是增加/减少焦点,按下 PAGE_UP/DOWN 将向上和向下滚动,表明回调未执行 event.preventDefault()。我将尝试创建一个重现此的小提琴。
    • 我做了一个重现问题的小提琴。你能试试吗? jsfiddle.net/fgzom4kw - 我也尝试了建议的解决方案。他们没有解决问题,但在小提琴中存在一些差异。键盘命令在有和没有 children() 和 find() 函数的情况下都可以工作,这不是我在我的机器上运行时看到的行为。
    • @Henry 我能够看到问题并着手解决。更新了我的答案:jsfiddle.net/Twisty/m1w2b7rv/39
    • 嘿,看来我们同时想通了。但与我的回答不同,您提供了一个活生生的例子。这将非常有用,我会将您的答案标记为已接受。谢谢您的帮助。顺便说一句,如果您删除答案的前半部分并只留下解决问题的部分,也许会更有用。任何未来的访问者都会更容易发现它。
    • 您的解决方案只有一个警告,我担心这可能会导致某些用户感到困惑。如果您专注于一篇文章并按 TAB,您将到达该文章中的锚点。如果您再次按 TAB 并且里面没有其他选项卡,那么您将被困在那里。离开该文章的唯一方法是通过 PAGE_UP/PAGE_DOWN。这不是 W3C 示例的工作方式。但我可以弄清楚如何解决这种行为。如果您愿意,您也可以尝试更新您的小提琴。
    【解决方案2】:

    我想通了。 And so did Twisty.

    答案在 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&lt;article&gt; 或(如示例中的)&lt;div role="article"&gt;,它将使用该元素。
    2. 如果event.target 不是文章,它将尝试检索它可以找到的“最接近”的文章。

    这意味着,如果文章中的交互式小部件在我们按下某个键盘命令时具有焦点,则将使用包含该小部件的文章。文章元素很特殊,因为它们包含在我的事件处理程序(以及屏幕阅读器)中使用的元数据(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


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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-06-14
      • 2022-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多