许多辅助技术的形式使用键盘导航来理解和对屏幕内容采取行动。 一种导航方式是通过 Tab 键。 如果你使用它来快速在表单上从一个输入跳到另一个输入,而无需使用鼠标或触控板,你可能已经熟悉这种导航方式了。
Tab 将按照它们在 DOM 中出现的顺序跳转到交互式元素。 这是为什么源代码的顺序与设计的视觉层次结构相匹配如此重要的原因之一。
可获得焦点的 交互式元素 列表是
- 锚点,当存在
href属性时, <button>,<input>和<textarea>,以及相应的 标签,<select>,<details>,<audio>和<video>,当存在控件时,<object>,取决于它的使用方式,- 在 Firefox 中具有滚动溢出的任何元素,
- 应用了
contenteditable属性的任何元素,以及 - 应用了
tabindex属性的任何元素(稍后将详细介绍此属性)。
交互式元素在以下情况下获得焦点
- 它已通过 Tab 键导航到,
- 它被点击,在链接到另一个可获得焦点的元素的锚点之后,
- 或者焦点通过
element.focus()在 JavaScript 中以编程方式设置。
焦点类似于用鼠标光标悬停在元素上,因为您正在标识要激活的元素。 也是 为什么视觉上明显的焦点样式如此重要。
焦点管理
焦点管理是指协调哪些元素可以接收焦点事件,哪些元素不能接收焦点事件的做法。 这是前端开发中比较棘手的方面之一,但对于提高网站和 Web 应用的无障碍性至关重要。
焦点管理的最佳实践
99% 的情况下,您都希望保持焦点顺序不变。 我再怎么强调这一点也不为过。
只要您使用 <button> 元素作为按钮、使用锚点元素作为链接、使用 <input> 元素作为用户输入等等,焦点就会自行运作,无需任何额外操作。
在极少数情况下,您可能希望将焦点应用到超出焦点顺序的元素,或使通常无法接收焦点事件的元素可获得焦点。 以下是一些关于如何以无障碍且直观的导航方式进行操作的指导原则
✅ 操作:了解 tabindex 属性
tabindex 允许元素获得焦点。 它接受一个整数作为值。 它的行为会根据使用的整数而有所不同。
❌ 不要:将 tabindex="0" 应用到不需要它的元素
可以接收键盘焦点的交互式元素(如 <button> 元素)不需要应用 tabindex 属性。
此外,您不需要在非交互式元素上声明 tabindex 来确保它们可以被辅助技术读取(事实上,如果不存在角色和可访问名称,这将是 WCAG 的失败)。 这样做实际上会为使用辅助技术的人 创建一个意外且难以导航的体验 — 他们有其他预期的方式来读取此内容。
✅ 操作:使用 tabindex="-1" 进行 JavaScript 焦点设置
tabindex="-1" 用于使用 JavaScript 创建可访问的交互式小部件。
声明 tabindex="-1" 将使元素通过 JavaScript 或点击/轻触获得焦点。 但是,它不会让元素通过 Tab 键进行导航。
❌ 不要:使用正整数作为 tabindex 值
这是一种严重的反模式。 使用正整数将覆盖预期的选项卡顺序,并为尝试导航内容的人创建一个混乱且令人迷失的体验。
这种情况发生一次就足够糟糕了。 多次声明就完全是噩梦。 认真说:不要这样做。
❌ 不要:创建手动焦点顺序
交互式元素仅凭其使用就可以被选项卡选中。 您不需要在一系列交互式元素上设置一系列具有递增值的 tabindex 属性,以便按照您认为人们应该在您的网站上使用的顺序进行导航。 相反,您可以让 DOM 中元素的顺序来完成此操作。
焦点捕获
可能有些时候您需要阻止元素获得焦点。 一个很好的例子是 焦点捕获,它是指有条件地将焦点事件限制在元素及其子元素的范围内的行为。
焦点捕获不要与 键盘陷阱(有时称为焦点陷阱)混淆。 键盘陷阱是指使用键盘导航的人无法从某个小部件或组件中退出,因为存在糟糕的循环逻辑。
您可以使用焦点捕获的一个实际示例是模态窗口
为什么这很重要?
将焦点保持在模态窗口内可以传达其边界,并帮助告知哪些是模态内容,哪些不是模态内容 — 这类似于视力正常的人如何看到模态窗口“漂浮”在其他网站或 Web 应用内容之上。 如果以下情况,这将是一个重要的信息:
- 您 视力低下或完全失明,并依赖屏幕阅读器来帮助传达交互模式的转变。
- 您视力低下,使用放大显示器,在这种情况下,模态窗口边界外的焦点可能会令人困惑且令人迷失。
- 您完全通过键盘进行导航,否则可能会从模态窗口中退出,并在尝试回到模态窗口时迷失在底层页面或视图中。
怎么做?
可靠地管理焦点是一件复杂的事情。 您需要使用 JavaScript 来
- 确定当前页面或视图上所有可获得焦点元素的容器元素。
- 确定被捕获内容的边界,包括第一个和最后一个可获得焦点的项目。
- 从被确定为可获得焦点的元素中删除交互性和可发现性,这些元素不在被捕获内容集中。
- 将焦点移动到被捕获的内容中。
- 监听信号表明要关闭被捕获内容的事件(保存、取消、关闭/点击Esc键等)。
- 当被相关事件触发时,关闭被捕获的内容区域。
- 恢复之前移除的交互性。
- 将焦点移回触发被捕获内容的交互元素。
为什么我们要这样做?
我不会说谎:这一切都很棘手,也很耗时。然而,焦点管理和合理的、可用的焦点顺序是一个Web 内容无障碍指南。它非常重要,以至于被认为是关于可用性的国际法律约束标准的一部分。
可Tab键操作且可发现
移除可发现性和交互性的方法有一些技巧。
屏幕阅读器具有交互模式,允许它们通过虚拟光标探索页面或视图。虚拟光标还允许使用屏幕阅读器的人发现页面中不可交互的部分(标题、列表等)。与使用Tab和焦点样式不同,虚拟光标仅供使用屏幕阅读器的人使用。
当您管理焦点时,您可能希望限制虚拟光标发现内容的能力。对于我们的模态示例,这意味着阻止用户在阅读模态时意外地“跳出”模态的边界。
可发现性可以通过明智地使用aria-hidden="true"来抑制。但是,交互性则更微妙一些。
进入inert
inert 属性是一个全局 HTML 属性,它将使移除和恢复交互元素的可发现性和焦点能力变得容易得多。以下是一个关于它如何工作的示例
<body>
<div
aria-labelledby="modal-title"
class="c-modal"
id="modal"
role="dialog"
tabindex="-1">
<div role="document">
<h2 id="modal-title">Save changes?</h2>
<p>The changes you have made will be lost if you do not save them.<p>
<button type="button">Save</button>
<button type="button">Discard</button>
</div>
</div>
<main inert>
<!-- ... -->
</main>
</body>
我故意避免在模态中使用<dialog>元素,因为它存在许多辅助技术支持问题。
inert已在保存确认模态后的<main>元素上声明。这意味着<main>中包含的所有内容都无法接收焦点,也无法点击。
焦点被限制在模态内部。当模态被关闭时,可以从<main>元素中移除inert。这种处理焦点捕获的方法比现有技术要容易得多。
请记住: 关闭事件可以由模态示例中的两个按钮引起,也可以由按下键盘上的Esc键引起。一些模态还允许您点击模态区域之外来关闭。
对 inert 的支持
最新版本的Edge、Chrome 和 Opera 都支持inert,当启用了实验性 Web 平台功能时。Firefox 支持也即将发布!唯一例外是 Safari 的桌面版和移动版。
我很希望看到 Apple 为inert实施原生支持。虽然提供了一个 polyfill,但它对所有主要屏幕阅读器都有非微不足道的支持问题。不太好!
此外,我想提请注意来自inert polyfill 项目的 README中的这条注释
与原生
inert实现相比,polyfill 在性能方面会很昂贵,因为它需要大量的树遍历。
树遍历意味着 polyfill 中的 JavaScript 可能需要大量计算能力才能工作,因此会降低最终用户的体验。
对于低功耗设备,如廉价的 Android 智能手机、旧笔记本电脑以及执行计算密集型任务(如运行多个 Electron 应用程序)的更强大的设备,这可能意味着会发生冻结或崩溃。原生浏览器支持意味着这种行为对设备的负担要轻得多,因为它可以访问 JavaScript 无法访问的浏览器部分。
Safari
就我个人而言,我对 Apple 缺乏对inert的支持感到失望。虽然我知道为浏览器添加新功能是一项非常复杂且困难的工作,但inert似乎是 Apple 应该更早支持的功能。
macOS 和 iOS 从历史上看对无障碍功能的支持一直很好,并且辅助技术友好功能是其营销活动中的一个常见部分。支持inert似乎是 Apple 使命的一个自然延伸,因为该功能本身会为简化可访问的 Web 体验的开发做出巨大贡献。
令人沮丧的是,Apple 对其正在开发的内容以及我们何时可以大体上预期看到它保持缄默。因此,inert的未来是一个开放性问题。
Igalia
Igalia是一家致力于浏览器功能的公司。他们目前正在进行一项实验,公众可以投票选择他们希望看到哪些功能。这项举措背后的原因超出了本文的范围,但您可以在Smashing Magazine 上阅读更多相关内容。
Igalia 正在考虑的一个功能是为 WebKit 添加对inert的支持。如果您一直在寻找一种改善网络无障碍功能的方法,但一直不确定如何开始,我鼓励您认捐。5 美元、10 美元、25 美元。无需太多,每一份捐款都有意义。
不幸的是,inert没有赢得开放优先级实验。这意味着我们仍然不知道 Apple 是否正在开发它,或者何时可以预期它出现在Safari 技术预览版中。
iOS 15.4 将在禁用的inert属性支持下发布。这是一个好消息,因为它表明 Apple 正在开发它。
总结
管理焦点需要一些技巧和细心,但非常值得这样做。inert属性可以极大地简化这一过程。
像inert这样的技术也代表了 Web 平台最强大的优势之一:能够为新出现的行为铺平道路,并将其编纂成易于使用且有效的东西。
进一步阅读
- 使用 tabindex 控制焦点(A11ycasts,第 04 集)
- 使用 tabindex 属性(The Paciello Group)
- 使用 JavaScript 在元素中捕获焦点(Hidde de Vries)
感谢Adrian Roselli 和Sarah Higley 的反馈。
我认为我们不应该鼓励 Igalia 及其倡议。
苹果是浏览器功能支持方面最糟糕的罪魁祸首,而且它是地球上最富有的公司……我们不需要为让其他人为 Safari 添加功能而付费。苹果需要从他们懒惰的屁股上爬起来,真正完成工作。
至于最受欢迎的浏览器 Chrome,他们实际上在推动许多新的标准,这很好。但我们当然也不需要在那里额外付费。他们在市值方面绝对处于食物链的顶端,他们非常富有……
Edge 很好,迁移到 Chromium 对他们帮助很大,即使这对 Web 标准来说有点不幸。但同样,这不像微软需要我们的钱,他们也是食物链的顶端,非常接近苹果。
如果你真的想鼓励 Web,鼓励 Firefox。谷歌通过雇佣他们的一些工程师,把他们榨干了,然后在削减了对 Firefox 的大量资金的同时,构建了自己的浏览器……
如果 Igalia 不是在为价值超过 1 万亿美元的公司制作的浏览器工作……我会没事的。但就目前而言,我找不到任何理由以任何方式帮助他们。
完全同意。
我为 Firefox 赞成+1。我从它首次发布以来就一直在使用它,它仍然是我最喜欢的浏览器。
您好 Eric,感谢您的介绍。
您是否知道为什么在每个元素上添加
inert以暂时标记为不可检测和非交互式,比将它添加到单个属性以(暂时)标记为文档中唯一应可检测和交互式的元素更受欢迎?作为
inert之类功能的替代方案,该功能尚未在浏览器中得到很好的支持,通过在小部件容器元素(例如,示例中的对话框div)上安装keydown处理程序来捕获焦点相对容易,并处理 Tab(和 Shift-Tab!)键。事件的target属性指示按键发生时哪个元素具有焦点。事件处理程序只需要知道哪些交互式元素在小部件中的制表顺序中是“第一个”和“最后一个”,以及当其中一个元素是target时,使用element.focus()将焦点移动到适当的“下一个”元素;否则,什么也不做,让事件传播。由于大多数需要焦点处理的复杂小部件将具有其他键处理需求,因此在其中包含 Tab 处理不会更难。这样做将焦点处理限制在小部件内的元素,而无需更改小部件外部的 DOM。
屏幕阅读器中的虚拟光标呢?
keydown处理程序也会处理吗?实施焦点捕获的另一种方法是使用 2 个捕获器作为捕获内容容器内第一个和最后一个可制表元素,例如
在 JS 中
我更希望采用一种选择加入的焦点捕获方法,例如,当存在
modal属性时,在包含元素内捕获焦点。从组件作者的角度来看,对话框如何知道在对话框激活时哪些元素应该变成
inert? 它不能。但它可以将modal附加到自身并以非常优雅的方式解决问题。