什么是无障碍

无障碍化开发-编程知识网
无障碍化,Accessibility(A11Y)是指无论健全人还是残疾人,年轻人还是老年人都可以从我们所开发的网站上获取信息和服务,让互联网访问公平变得可能。
建设无障碍化网站是一项非常有意义且正确的事情,不单单是可以增加网站的受益群众,更是一个有情怀、有担当的开发者应该关注的方面,甚至在一些国家与地区支持无障碍化已经写入法律规定中。

在 W3C 上有一句话——“Web 根本目的是为了为所有的人服务,而不是受限于的硬件、软件、语言、文化、位置或身体或精神能力”(原文:The Web is fundamentally designed to work for all people, whatever their hardware, software, language, location, or ability…)

那么,即使只有部分浏览障碍的用户,我们在开发中也需要将他们的使用考虑到,可我们日常需要考虑的障碍类型有哪些呢?

用户障碍类型

根据 W3C 所著Web 内容无障碍指南(WCAG)中提到的,主要的障碍类型有一下四类:
类型 介绍

视力障碍

指视力下降到一定程度,可能无法通过常规手段(眼镜)解决,主要包括盲人、低视力、色盲、色弱等用户。

认知障碍

是一种范围广泛的残疾类型,从能力最有限的智障人士到我们随着年龄增长和思考和记忆困难而出现的所有人。 主要包括患有精神疾病用户、癫痫病用户、因网站布局不一致引起的不良反应用户、学习障碍用户、阅读障碍用户、注意力缺陷用户、多动障碍用户等。

行动障碍

指无法使用他/她的肢体,或缺乏行走、抓取或抬起物体的力量。

听力障碍

指听觉部分或完全丧失,可能会使用 ATs,但在 Web 中并没有专门 ATs 可以使用

上述四种障碍类型中,视力障碍和认知障碍已有成熟的辅助软件或者开发守则,帮助我们解决或降低这些用户对网站使用困难问题。我们需要做到的是,在开发中遵守这些守则,根据 WCAG 尽最大的能力去完善网站支持这些辅助软件的使用。

视力障碍用户

会通过辅助工具来了解并使用网站。常见的辅助工具有放大镜,屏幕缩放以及屏幕阅读器。其中较为常用的屏幕阅读器有:

付费的产品:JAWS (Windows) 和 Dolphin Screen Reader (Windows)。
免费的产品:NVDA (Windows),ChromeVox (Chrome, Windows and Mac OS X)和 Orca (Linux)。
内置的产品:VoiceOver (macOS, iPadOS, iOS),Narrator (Microsoft Windows,ChromeVox (on Chrome OS)和 TalkBack (Android).
认知障碍用户,会因为我们遵守开发守则,而更容易使用网站。在MDN上有为认知障碍者总结的开发守则:

用多种方式展示内容,例如通过文本,语音或视频;
编写易于理解的内容,例如少用方言或者颜文字;
重要的内容要细心写;
尽量减少干扰,例如一些没什么用的功能与广告;
网页布局与导航要一致;
常规的元素样式,例如带下划线的链接(未访问时为蓝色)和访问时为紫色;
流程交互要具有进度以及步骤指示;
用户权限认证方式要简单;
错误信息要展示清楚;
表单要便于填写与操作

为什么大部分网站都不能自动支持无障碍?

这个答案相当于我们需要知道辅助工具软件是如何对网站进行识别?大部分的网站为什么不能被辅助软件识别?我们可以通过这两个问题去知晓大部分网站不能自动支持无障碍化的原因。

辅助工具软件如何识别网站?

当浏览器在渲染的时候构建 DOM 树,并根据 DOM 树上每个节点元素的语义,转换为 Accessibility Tree(无障碍树)。而,辅助工具识别的内容,就是这个无障碍树,按照 DOM 节点的顺序,而非视觉顺序从上往下依次进行读屏。无障碍树,我们可以通过打开控制台的 Elements -> Accessibility 中查看到:
无障碍化开发-编程知识网

为什么有些网站不能被辅助软件识别?

浏览器可以将 DOM 树转变成无障碍树,是因为 DOM 树上大部分内容具有隐式语义含义。也就是说,采用的原生 HTML 元素能够被辅助软件识别,并且可以预测其在各类平台上的工作方式。因此,链接或按钮等原生 HTML 元素的无障碍功能可自动得到处理,被辅助软件识别。

但,随着网络应用变得越来越复杂和动态。现在,我们采用的元素虽然看上去像原生元素,实际却并非如此,如下图的按钮:
无障碍化开发-编程知识网
此“按钮”的实现方式多种多样,下面是其中一种方法:

<div class="button-fack">click button</div>

此“按钮”我们肉眼可见确实是一个按钮。但,

– The Content Division element(内容的分割元素),是作为一个“纯”容器,该元素不代表任何东西。所以, div虽然有含义,但没语义,辅助工具是无法对其识别。而且,其还无法通过键盘对焦,只能通过鼠标点击对焦。

也就是因为这种有含义无语义的元素,替换原生的 HTML 元素实现它们的功能,造成这些网站无法被辅助工具自动识别。

那么,我们应该如何去改造已有的网站,使其支持无障碍化?

怎么改造网站支持无障碍?

根据 W3C 所著Web 内容无障碍指南(WCAG)可访问的富 Internet 应用程序(WAI-ARIA),对 DOM 树上元素的语义(元素角色、状态与属性)、焦点进行补充完善,从而达到对网站的无障碍化改造。

WCAG

Web 内容可访问性指南(WCAG)是由互联网的主要国际标准组织万维网联盟(W3C)的 Web 可访问性倡议(WAI)发布的一系列 Web 可访问性指南的一部分。

它们是一组使 Web 内容更易于访问的建议,主要针对残疾人,但也适用于所有用户代理,包括高度受限的设备,如移动电话。WCAG2.0,于 2008 年 12 月发布,并于 2012 年 10 月成为国际标准化组织标准,ISO/IEC 40500:2012。WCAG 2.1 于 2018 年 6 月成为 W3C 推荐标准。
WCAG 主要有如下四大原则:
无障碍化开发-编程知识网

WAI-ARIA

WAI-ARIA(可访问的富 Internet 应用程序套件)是由万维网联盟(W3C)发布的一项关于 A11Y 技术应用规范。该规范定义了一种使残障人士更易于访问 Web 内容和 Web 应用程序的方法,增加 HTML,JavaScript 和相关技术开发的网站动态内容以及用户界面组件的可访问性。

WAI-ARIA 主要就是针对于上述的障碍类型以及 WCAG 要求的技术细则,它定义了一组附加的 HTML 属性,可将这些属性应用于元素以提供附加的语义并在缺少的地方,从而改变并完善 Accessibility Tree。运用好这些属性、技术细则,那么浏览障碍人士便能轻松访问我们的应用。在 WAI-ARIA 规范中,定义了三个主要功能与特征:角色、状态与属性:

角色

角色,是指通过在元素上设置 role 属性方式确定元素具体角色,其作用是用于定义元素是什么或做什么。且,在WAI-ARIA Role中有详细介绍每个角色与其对应的属性是如何协作,以及如何以浏览器和辅助技术支持的方式使用它们。
无障碍化开发-编程知识网
如上图,role 可选属性值有点多,但其实主要就分了四类:

  • 抽象角色

  • 小部件角色

  • 文件结构角色

  • 地标角色
    其主要作用为:

  • 角色信息描述。

  • 相关角色的等级信息。

  • 角色上下文。

  • 引用其他规范中的相关概念。

  • 使用 OWL(Web Ontology Language)提供允许语义继承的类型层次结构。

  • 每个角色支持的状态和属性。

举一个例子,进行角色改造:

以上述的“ click button ”按钮为例,该功能应为按钮,但由于使用 div 导致其无语义。对其改造,应添加角色为 button,使其被对焦时,辅助软件可以识别出这是一个按钮。


// 角色改造前,对焦时无法得知其语义
<div class="button-fack">click button</div>
// 角色改造后,对焦时辅助工具感知其为按钮
<div role="button" class="button-fack">click button</div>

状态与属性

WAI-ARIA 提供了可访问性状态和属性的集合,这些状态和属性用于支持各种操作系统平台上的平台可访问性 API,使我们可以对任何页面元素对无障碍树进行细微(乃至彻底的)修改。辅助技术可以通过公共的用户代理应用(例如读屏软件)DOM 或通过映射到平台可访问性 API 来访问这些信息。

当状态、属性、角色结合时,用户代理应用可以为辅助技术提供用户界面信息,以便随时传递给用户。状态或属性的更改将导致向辅助技术发出通知,这可能会警告用户发生了更改。WAI-ARIA 将状态与属性分为以下四类:
当状态、属性、角色结合时,用户代理应用可以为辅助技术提供用户界面信息,以便随时传递给用户。状态或属性的更改将导致向辅助技术发出通知,这可能会警告用户发生了更改。WAI-ARIA 将状态与属性分为以下四类:
无障碍化开发-编程知识网
对于上述属性中,找几个常用的属性并举一个例子,让大家来了解一下它们的神奇之处:

例子 aria-label div 与 css 结合实现的图片

<div id="image"/>// css代码:
#image {width: 200px;height: 150px;background: url(https://docs.idqqimg.com/tim/docs/desktop/image/list_logo-230232562f.svg);background-size: contain;background-repeat: no-repeat;
}

按照上述写法,当图片对焦时,辅助软件会识别出什么? –> “组”。因为 div 元素有含义无语义,只是一个单纯的内容分割元素,所以,它被识别为“组”。

那我们应该怎么改造让辅助软件识别的内容和我们肉眼看到的一致?

1、需要给此 div 元素赋予角色 role,告诉辅助软件这个是一个图片;

2、需要对图片添加图片内容说明,告诉辅助软件这个是一个什么图片。此时,我们需要用到 aria-label 属性。
aria-label 属性是用来解释当前元素的内容,但,不可在也有说明(如 alt、tiltle 等)的情况下添加,会造成冲突。辅助软件会将所有说明串接在一起,将会造成重复性对当前节点说明。

改造后的代码如下所示:


<div id="image" role="img" tabIndex="" aria-label="腾讯文档log"/>

无障碍化开发-编程知识网

辅助工具会识别其为“腾讯文档 log 图像”。实际上如果我们用 html 原生元素 img ,只需要添加 alt 属性,即可。


<img src="https://docs.idqqimg.com/tim/docs/desktop/image/list_logo-230232562f.svg" alt="腾讯

通过上述内容,相比大家已经对 WAI-ARIA 已经有了大致了解,它只能对无障碍树进行修改,补充元素语义,不能添加元素可对焦能力。所以,我们还需要额外的开发 DOM 树的焦点控制、导航能力

焦点管理

WAI-ARIA 解决了元素无含义问题,焦点管理就是来解决 WCAG 的可操作性问题。通过焦点管理,让所有交互都是可聚焦,可通过键盘进行操作。

从上述内容中,可知:

辅助工具是依靠无障碍树对网站进行识别,而无障碍树是在 DOM 树基础上结合语义含义生成的。
辅助工具对网站内容识别的顺序,是按照 DOM 树的结构从上到下依次进行对焦识别,而非网站页面显示视觉顺序。
所以,网站的 DOM 顺序非常重要!但,我们的网站现在已经开发完毕,为了支持无障碍化能力,去改变已有的 dom 节点顺序,消耗人力、时间都很大,所以,直接从根源上解决识别顺序问题是不可能的,只能在以后的开发中尽量按照视觉效果在 DOM 树上对应位置添加 dom 节点。那么,我们就有以下三个问题:

如何让辅助工具按照页面显示顺序进行识别?
如何让辅助工具对一些 DOM 节点(网站装饰节点)不进行对焦识别?
如何让一些元素进行对焦呢?

使用 tabIndex 进行焦点管理

tabIndex 顾名思义就是指通过 按 tab 键,对元素进行对焦的顺序。

tabIndex 的值可以分为以下 3 类:
上述的情况下,当出现多个 tabIndex 值为一致时,将按 DOM 树的顺序依次对焦。

我在进行使用时,对上述[-1, 32767]以外的范围充满了好奇,因此进行了一些实验发现:

如果,我设置 tabIndex=-2,和-1 一样的效果无法通过 tab 按键进行对焦。如果,我设置 tabIndex=32768,超过最大值,浏览器不识别,但是如果继续往上增大 tabIndex=123456,则会发生和-1 的一样效果无法通过 tab 键盘对焦。所以,建议大家还是老老实实的写[-1, 32767],以防浏览器识别错误。

通过上述的解释,并不是很清楚具体的表现,那么我们来举一个例子,来实践一下 tabIndex 控制焦点:


<a href="https://docs.qq.com/desktop" tabindex="-1" title="无法被tab对焦"> tabIndex="-1" </a>
<a href="https://docs.qq.com/desktop" title="链接1"> tabIndex="0" </a>
<a href="https://docs.qq.com/desktop" tabIndex="1" title="链接2"> tabIndex="1" </a>
<a href="https://docs.qq.com/desktop" tabIndex="3" title="链接3"> tabIndex="3" </a>
<div tabIndex="2" id="divTabIndexTest">红色的是一个div 且 tabIndex="2"<br /><a href="https://docs.qq.com/desktop" tabIndex="0" title="链接4"> tabIndex="0" </a><br /><a href="https://docs.qq.com/desktop" tabIndex="1" title="链接5"> tabIndex="1" </a><br /><a href="https://docs.qq.com/desktop" tabIndex="2" title="链接6"> tabIndex="2" </a></div>
<a tabIndex="2" href="https://docs.qq.com/desktop" title="链接7"> tabIndex="2" </a>

无障碍化开发-编程知识网
先将所有的 tabIndex="1"的元素,按照 DOM 树的顺序依次对焦后,再去对焦 2、3 最后对焦 0 。-1 的元素无法使用 tab 键进行对焦。

canvas、繁多 dom 无障碍化

对内容区域进行统一处理,这个统一监听用户操作事件即可,但是怎么控制读屏软件朗读呢?我们分为以下三步:

首先,我们需要解决如何让一个节点发生变化,读屏软件就能立即对其内容进行读屏?设置 aria-live 属性,其属性值与作用如下:
无障碍化开发-编程知识网
然后,要解决如何让读屏软件完整的朗读变动的 dom 节点内容呢?设置 aira-atomic 属性,该属性值 true/ false 是控制朗读 dom 节点全部内容,还是只朗读发生变动的内容。

最后,这个存储用户操作结果的节点放到哪里呢?放到一个用户看不到的隐藏 dom 节点里,一个专门为读屏软件而创建的隐藏 dom 节点。详细代码如下所示:
无障碍化开发-编程知识网
具体解决方案(以用户对内容进行选区操作为例)如下图所示:
无障碍化开发-编程知识网
上图具体步骤如下所示:

用户对内容区域进行操作;
判断用户操作结果,抛出用户操作(accessibility)事件;
监听 accessibility 事件,获取用户操作结果;
将用户操作结果作为 a11yContentChange 事件参数抛出;
Accesibility 组件监听到 a11yContentChange 事件后,将隐藏 dom 节点内容替换为事件传递参数;
读屏软件监听到隐藏 dom 节点内容变动,朗读节点内容。
上述是让读屏软件读出用户的操作结果,但是用户内容区域进行操作时,可能会识别到背景水印、底纹等,显示在内容区域但是不想让用户在内容区域操作时知道的功能,那如何解决这个问题呢?我们需要对这些节点进行屏蔽。

屏蔽多余内容

对节点进行屏蔽,我们需要考虑屏蔽的是什么?是无法通过键盘对焦?还是让非交互内容从可读性树上移除?

例如上述说的对文档内容区域的水印、底纹等非交互性内容进行朗读屏蔽,即将其可访问性树上移除。我们在其节点上,添加 aria-hidden 属性,设为 true 从而控制其与子元素从可访问性树上移除,让读屏软件无法对其识别朗读。

但,对于像 iframe 这种外部插件,可交互内容,我们需要通过对 iframe 先屏蔽键盘对焦操作,设置 tabindex=-1;再对 iframe 内部非交互性元素设置 aria-hidden 属性。

上述的内容支持读屏、屏蔽多余内容都是对内容区域进行改造,下面我们来介绍以下对功能区域的改造。

焦点导航

功能区域就是用户对文档的控制交互部分,里面有涉及到弹出面板、关闭面板、打开下拉菜单、退出下拉菜单、选中、取消选中等等交互操作。想要障碍用户同普通用户一样使用,其实很简单,只需要做到以下四点:

每个交互功能可通过 tab 按键对焦;
可以被读屏软件朗读出该功能的具体作用与操作;
按键控制子功能开关;
功能切换时焦点正常移动;

tab 对焦

若交互功能是由 button 等原生 html 交互元素实现的,都可以自动支持对焦,我们无须对其进行二次改造。

但,文档里面很多都不是交互元素实现的。例如按钮,大部分都是通过 svg 元素实现的,不支持对焦。我们需要通过设置 tabindex 强行控制其可以支持 tab 键对焦。

功能语义

设置每个功能可对焦读屏后,我们需要保证其都能够被读屏软件朗读出,其作用与操作方法。

我们需要每个非原生 html 实现的元素,添加 role 属性,使得其角色正确。设置 aria-label 对每个功能节点添加功能注释,确保障碍用户可以像普通用户一样明白理解该功能。

子功能开关控制

功能区域有常见的打开面板、打开下来列表等等打开另外一个子功能的操作。例如,当障碍用户错误打开某个面板时,需要对整个面板进行全部对焦后,才能找到关闭按钮关闭面板。若面板内容繁多,那岂不是需要障碍用户听完所有内容后,才能找到关闭按钮,大概半个小时过去了吧…那么,如何让障碍用户快速打开与关闭子功能呢?

我们调研了一下互联网中已经支持无障碍的网站,发现它们通用的是 esc 退出子功能、空格键盘和回撤键打开子功能。所以,我们希望和它们保持统一,避免障碍用户的操作不适应。

焦点控制

我们为什么要对功能实现焦点控制?

首先,我们需要达成共识:普通用户可以看到整个页面概况,但障碍用户只能通过辅助工具(读屏软件)给予的提示得知当前功能。那么,代表着障碍用户只知道当前焦点位置的功能。

其次,我们要知道网站交互功能里面涉及到弹出面板、关闭面板、打开下拉菜单、退出下拉菜单、选中、取消选中等复杂交互操作。

那么结合上述两点,我们得出若障碍用户在打开某个功能时,不进行焦点控制让其焦点可以对焦到网站所有位置,那么会造成障碍用户以为所有可以对焦的内容都是属于该功能!产生严重的歧义!举一个例子让大家感受一下,下图是进入高级设置面板后的读屏操作,橙色方框就是读屏软件读取的内容。
如果我们不对“高级权限设置”面板焦点进行控制,那么就如上图所示,障碍用户跟着焦点获取面板内容,结果焦点整个网站到处跑!请问他们怎么知道哪些是面板的?哪些是非面板内容呢?因此,造成障碍用户和普通用户对功能产生严重歧义!

例如,对目录进行焦点控制:

障碍用户在打开目录功能时,我们需要对当前位置进行记录,然后获取目录数量。
障碍用户对目录进行 tab 键对焦时,我们需要进行条件判断操作前是否为最后一个目录标题,是,则强制 focus()到第一个目录标题;否,则让其按序自然对焦。
若用户对某个目录标题进行确认,则将焦点移到对应位置,同时屏蔽/关闭整个目录防止误触,直到用户通过开启目录开关再将焦点移动到目录。
若用户未选中任何目录标题,按 esc 退出目录功能,那么我们需要将焦点移到打开目录时的位置!

总结

对上文的内容进行总结归纳,希望在以后的开发中大家能够注意:

  1. 在 DOM 树上新添加的节点位置,最好与视觉位置一一对应,防止辅助工具识别顺序错误;
  2. 开发中尽量遵守认知障碍者在 MDN上总结的开发守则;
  3. 对交互功能元素节点,添加功能说明;
  4. 面板、弹窗等交互需要在退出功能后,焦点需要回到原本触发位置;
  5. 对焦点过于复杂或无法获取具体操作,可通过添加隐藏 dom 节点,设置 aira-live、aira-atomic、aria-hidden 去做统一对用户操作进行处理;
  6. 多余节点或不需要障碍用户知道的节点,非交互性节点可以通过 aira-hidden 进行屏蔽,若交互性节点还需要添加 tabIndex=-1 进行按键对焦屏蔽。

当你对网站进行无障碍化改造时,切记不要为了无障碍而强行改动大量代码,造成网站页面变动、网站性能降低、后期难以维护、普通用户使用不适等负面影响。无障碍改造是锦上添花,不是兰芷萧艾!