可键盘导航的 JavaScript 组件
Web 应用程序通常使用 JavaScript 来模仿桌面部件,例如菜单、树状视图、富文本字段和选项卡面板。这些部件通常由 <div>
和 <span>
元素组成,这些元素本质上不提供与其桌面对应物相同的键盘功能。本文档描述了使 JavaScript 部件可键盘访问的技术。
使用 tabindex
默认情况下,当人们使用 Tab 键浏览网页时,只有交互式元素(如链接、表单控件)才能获得焦点。通过 tabindex
全局属性,作者也可以使其他元素获得焦点。当设置为 0
时,元素可以通过键盘和脚本获得焦点。当设置为 -1
时,元素可以通过脚本获得焦点,但它不会成为键盘焦点顺序的一部分。
使用键盘时元素获得焦点的顺序,默认是源顺序。在特殊情况下,作者可能希望重新定义顺序。为此,作者可以将 tabindex
设置为任何正数。
警告: 避免为 tabindex
使用正值。具有正 tabindex
的元素会出现在页面上默认交互式元素之前,这意味着页面作者在使用一个或多个正 tabindex
值时,必须为页面上所有可聚焦的元素设置(并维护)tabindex
值。
下表描述了现代浏览器中 tabindex
的行为
tabindex 属性 |
可通过鼠标或 JavaScript (element.focus() ) 聚焦 |
Tab 可导航 |
---|---|---|
不存在 | 遵循元素的平台约定(表单控件、链接等为“是”)。 | 遵循元素的平台约定。 |
负值(例如 tabindex="-1" ) |
是 | 否;作者必须通过 focus() 方法响应箭头键或其他按键来聚焦该元素。 |
零(例如 tabindex="0" ) |
是 | 在文档中相对于元素位置的 Tab 顺序中(请注意,<a> 等交互式元素默认具有此行为,它们不需要该属性)。 |
正值(例如 tabindex="33" ) |
是 | tabindex 值决定了该元素在 Tab 顺序中的位置:较小的值将比较大的值更早地将元素放置在 Tab 顺序中(例如,tabindex="7" 将出现在 tabindex="11" 之前)。 |
非原生控件
像 <a>
、<input>
和 <select>
这样的原生 HTML 交互式元素已经是键盘可访问的,因此使用它们是使组件支持键盘的最快途径。
通过添加 tabindex
的 0
值,作者也可以使 <div>
或 <span>
元素键盘可访问。这对于使用 HTML 中不存在的交互式元素的组件特别有用。
分组控件
对于菜单、选项卡列表、网格或树状视图等组件的父元素分组,父元素应在 Tab 顺序中(tabindex="0"
),并且每个后代选项/选项卡/单元格/行都应从 Tab 顺序中移除(tabindex="-1"
)。用户应能够使用箭头键导航后代元素。(有关典型小部件通常预期的键盘支持的完整描述,请参阅 WAI-ARIA Authoring Practices。)
下面的示例展示了这种技术在嵌套菜单控件中的使用。一旦键盘焦点落在包含的 <ul>
元素上,JavaScript 开发人员就必须以编程方式管理焦点并响应箭头键。有关在组件内管理焦点的方法,请参阅下面的“管理组内焦点”。
<ul id="mb1" tabindex="0">
<li id="mb1_menu1" tabindex="-1">
Font
<ul id="fontMenu" title="Font" tabindex="-1">
<li id="sans-serif" tabindex="-1">Sans-serif</li>
<li id="serif" tabindex="-1">Serif</li>
<li id="monospace" tabindex="-1">Monospace</li>
<li id="fantasy" tabindex="-1">Fantasy</li>
</ul>
</li>
<li id="mb1_menu2" tabindex="-1">
Style
<ul id="styleMenu" title="Style" tabindex="-1">
<li id="italic" tabindex="-1">Italics</li>
<li id="bold" tabindex="-1">Bold</li>
<li id="underline" tabindex="-1">Underlined</li>
</ul>
</li>
<li id="mb1_menu3" tabindex="-1">
Justification
<ul id="justificationMenu" title="Justification" tabindex="-1">
<li id="left" tabindex="-1">Left</li>
<li id="center" tabindex="-1">Centered</li>
<li id="right" tabindex="-1">Right</li>
<li id="justify" tabindex="-1">Justify</li>
</ul>
</li>
</ul>
禁用控件
当自定义控件变为禁用状态时,通过将 tabindex="-1"
设置为 -1
来将其从 Tab 顺序中移除。请注意,分组控件(如菜单中的菜单项)中的禁用项应仍可通过箭头键导航。
管理组内焦点
当用户通过 Tab 键离开某个组件并返回时,焦点应返回到之前具有焦点的特定元素,例如树状项或网格单元格。有两种方法可以实现此目的:
- 漫游 tabindex:以编程方式移动焦点
aria-activedescendant
:管理“虚拟”焦点
技术 1:漫游 tabindex
将当前焦点的元素的 tabindex
设置为“0”可确保,如果用户通过 Tab 键离开组件然后返回,组内选定的项目将保留焦点。请注意,将 tabindex
更新为“0”还需要将先前选定的项目更新为 tabindex="-1"
。此技术涉及响应按键事件以编程方式移动焦点,并更新 tabindex
以反映当前具有焦点的项目。为此:
为组中的每个元素绑定一个按键处理程序,当使用箭头键移动到另一个元素时:
- 以编程方式将焦点应用于新元素,
- 将当前焦点元素的
tabindex
更新为“0”,以及 - 将先前焦点元素的
tabindex
更新为“-1”。
技术 2:aria-activedescendant
此技术涉及将单个事件处理程序绑定到容器组件,并使用 aria-activedescendant
来跟踪“虚拟”焦点。(有关 ARIA 的更多信息,请参阅此 可访问 Web 应用程序和组件概述。)
aria-activedescendant
属性标识当前具有虚拟焦点的后代元素的 ID。容器上的事件处理程序必须通过更新 aria-activedescendant
的值来响应按键和鼠标事件,并确保当前项目被适当地设置样式(例如,通过边框或背景颜色)。
一般准则
焦点事件的使用
- 请勿分派
focus
事件来将焦点发送到某个元素。DOM 焦点事件仅用于信息:它们在某个元素获得焦点后由系统生成,但实际上不用于设置焦点。请使用element.focus()
代替。 - 请监听
focus
和blur
事件来跟踪焦点变化。不要假设所有焦点变化都来自按键和鼠标事件:辅助技术(如屏幕阅读器)可以将焦点设置到任何可聚焦的元素。如果您想跟踪整个文档的焦点状态,可以使用document.activeElement
来获取活动元素,或者使用document.hasFocus
来确保当前文档是否具有焦点。
确保键盘和鼠标产生相同的体验
为了确保用户体验无论输入设备如何都保持一致,键盘和鼠标事件处理程序应在适当的地方共享代码。例如,当用户使用箭头键导航时更新 tabindex
或样式的代码也应被鼠标点击处理程序使用,以产生相同的更改。
确保键盘可用于激活元素
为了确保键盘可用于激活元素,绑定到鼠标事件的所有处理程序也应绑定到键盘事件。例如,为了确保 Enter 键可以激活元素,如果您有一个 onclick="doSomething()"
,您也应该将 doSomething()
绑定到按键事件:onkeydown="event.code === "Enter" && doSomething();"
。
始终为 tabindex="-1" 的项目和以编程方式获得焦点的元素绘制焦点
确保获得焦点的元素具有焦点环。这可以通过 CSS outline
属性来完成,该属性不应被无条件设置为 none
— 如果您想阻止显示不必要的焦点环,请使用 :focus-visible
伪类。
阻止使用的按键事件执行浏览器功能
如果您的组件处理某个按键事件,请通过使用事件处理程序的返回值来阻止浏览器也处理它(例如,响应箭头键的滚动)。如果您的事件处理程序返回 false
,事件将不会传播到您的处理程序之外。
例如
<span tabindex="-1">…</span>
span.onkeydown = handleKeyDown;
如果 handleKeyDown()
返回 false
,事件将被捕获,阻止浏览器根据按键执行任何操作。
此时,不要依赖按键重复的一致行为
不幸的是,onkeydown
是否重复取决于您运行的浏览器和操作系统。