支持键盘导航的 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 元素已经可以通过键盘访问,因此使用其中一个是最快的使组件能够与键盘协同工作的方法。

作者还可以通过添加 tabindex0 来使 <div><span> 可通过键盘访问。这对于使用 HTML 中不存在的交互式元素的组件特别有用。

分组控件

对于菜单、选项卡列表、网格或树形视图等分组组件,父元素应位于 Tab 顺序中(tabindex="0"),并且每个子元素选择/选项卡/单元格/行应从 Tab 顺序中移除(tabindex="-1")。用户应该能够使用箭头键导航子元素。(有关通常预期用于典型组件的键盘支持的完整描述,请参阅 WAI-ARIA 创作实践)。

下面的示例展示了这种技术如何与嵌套菜单控件一起使用。一旦键盘焦点落在包含 <ul> 元素上,JavaScript 开发人员必须以编程方式管理焦点并响应箭头键。有关在组件内管理焦点的技术,请参阅下面的“管理组内的焦点”。

html
<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" 将其从 Tab 顺序中移除。请注意,分组组件中的禁用项(例如菜单中的菜单项)应能够继续使用箭头键进行导航。

管理组内的焦点

当用户从组件切换到其他位置再返回时,焦点应返回到具有焦点的特定元素,例如树项或网格单元格。有两种技术可以实现这一点

  1. 游动 tabindex:以编程方式移动焦点
  2. aria-activedescendant:管理“虚拟”焦点

技术 1:游动 tabindex

将聚焦元素的 tabindex 设置为“0”可确保如果用户从组件切换到其他位置再返回,组内的选中项将保留焦点。请注意,将 tabindex 更新为“0”也需要将先前选中的项更新为 tabindex="-1"。此技术涉及以编程方式响应按键事件移动焦点并更新 tabindex 以反映当前聚焦的项。为此

为组中的每个元素绑定一个键盘按下处理程序,当使用箭头键移动到另一个元素时

  1. 以编程方式将焦点应用于新元素,
  2. 将聚焦元素的tabindex更新为“0”,以及
  3. 将先前聚焦元素的tabindex更新为“-1”。

这是一个使用此技术的WAI-ARIA 树视图示例。

提示

使用element.focus()设置焦点

不要使用createEvent()initEvent()dispatchEvent()将焦点发送到元素。DOM 焦点事件仅被视为信息性事件:在某个元素获得焦点后由系统生成,但实际上不用于设置焦点。请改用element.focus()

使用onfocus跟踪当前焦点

不要假设所有焦点更改都来自键盘和鼠标事件:屏幕阅读器等辅助技术可以将焦点设置为任何可聚焦元素。请改用onfocusonblur跟踪焦点。

onfocusonblur现在可以与每个元素一起使用。没有标准的DOM接口来获取当前文档焦点。如果要跟踪焦点状态,可以使用document.activeElement获取活动元素。您还可以使用document.hasFocus来确保当前文档焦点。

技术 2:aria-activedescendant

此技术涉及将单个事件处理程序绑定到容器小部件,并使用aria-activedescendant跟踪“虚拟”焦点。(有关 ARIA 的更多信息,请参阅此可访问 Web 应用程序和小部件概述。)

aria-activedescendant属性标识当前具有虚拟焦点的后代元素的 ID。容器上的事件处理程序必须响应键和鼠标事件,方法是更新aria-activedescendant的值并确保当前项目以适当的方式设置样式(例如,使用边框或背景颜色)。

通用指南

使用onkeydown捕获键盘事件,而不是onkeypress

IE 不会为非字母数字键触发keypress事件。请改用onkeydown

确保键盘和鼠标产生相同的体验

为了确保用户体验在任何输入设备下都保持一致,键盘和鼠标事件处理程序应在适当的情况下共享代码。例如,当用户使用箭头键导航时更新tabindex或样式的代码也应由鼠标单击处理程序使用以产生相同的更改。

确保可以使用键盘激活元素

为了确保可以使用键盘激活元素,绑定到鼠标事件的任何处理程序也应绑定到键盘事件。例如,为了确保 Enter 键可以激活元素,如果您有onclick="doSomething()",则还应将doSomething()绑定到键盘按下事件:onkeydown="event.code === "Enter" && doSomething();"

始终为tabindex="-1"的项目和以编程方式接收焦点的元素绘制焦点

IE 不会自动为以编程方式接收焦点的项目绘制焦点轮廓。在更改背景颜色(例如this.style.backgroundColor = "gray";)或添加点状边框(例如this.style.border = "1px dotted invert")之间进行选择。在点状边框的情况下,您需要确保这些元素最初具有不可见的 1px 边框,以便在应用边框样式时元素不会增长(边框占用空间,而 IE 没有实现 CSS 轮廓)。

防止使用的键盘事件执行浏览器功能

如果您的窗口小部件处理键盘事件,请使用事件处理程序的返回值阻止浏览器也处理它(例如,响应箭头键滚动)。如果您的事件处理程序返回false,则该事件将不会传播到您的处理程序之外。

例如

html
<span tabindex="-1" onkeydown="return handleKeyDown();"></span>

如果handleKeyDown()返回false,则该事件将被消耗,阻止浏览器根据按键执行任何操作。

目前不要依赖于按键重复的一致行为

不幸的是,onkeydown可能会或可能不会重复,具体取决于您正在运行的浏览器和操作系统。