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
)也获得焦点。我说得对吗?
我尝试过的事情:
- 委托事件,使用 on() 捕获
#product-grid
中某些元素的 keydown 事件,包括锚元素。
- 直接将 keydown 事件处理程序附加到
<article>
中的锚点。当我将焦点放在它上面并按下一个键时,什么也没有发生。根据我在 SO 上搜索的结果,这应该是可行的。
这是一个 fiddle,您可以在其中重现问题: https://jsfiddle.net/fgzom4kw/
重现问题:
- 点击任何项目的灰色背景(不是锚点)。红色轮廓表示它已聚焦。
- 使用 PAGE UP 和 PAGE DOWN 浏览项目。
- 按 TAB 键,这会将焦点设置在其中一个锚点上。
- 选中锚点后,尝试使用 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"]');
它试图找出在按键时哪个元素获得了焦点。
- 如果
event.target
是 <article>
或(如示例中的)<div role="article">
,它将使用该元素。
- 如果
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/
使应用程序符合 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
)也获得焦点。我说得对吗?
我尝试过的事情:
- 委托事件,使用 on() 捕获
#product-grid
中某些元素的 keydown 事件,包括锚元素。 - 直接将 keydown 事件处理程序附加到
<article>
中的锚点。当我将焦点放在它上面并按下一个键时,什么也没有发生。根据我在 SO 上搜索的结果,这应该是可行的。
这是一个 fiddle,您可以在其中重现问题: https://jsfiddle.net/fgzom4kw/
重现问题:
- 点击任何项目的灰色背景(不是锚点)。红色轮廓表示它已聚焦。
- 使用 PAGE UP 和 PAGE DOWN 浏览项目。
- 按 TAB 键,这会将焦点设置在其中一个锚点上。
- 选中锚点后,尝试使用 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"]');
它试图找出在按键时哪个元素获得了焦点。
- 如果
event.target
是<article>
或(如示例中的)<div role="article">
,它将使用该元素。 - 如果
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/