WAI-ARIA无障碍web规范
W3C的Web信息无障碍计划 (WAI)与全球行业一起致力于为残障人士和年长用户打造一个更易于访问的万维网。WAI通过保证Web技术队无障碍的支持,开发Web内容、浏览器、媒体播放器以及编辑工具的开发指南,开发资源以支持改善评估工具,开发教育与宣传的资源,与工业界及科研院所合作,以促进Web无障碍事业的发展。
更多关于WAI的信息请见 http://www.w3.org/WAI/
1. 无障碍设计
无障碍设计是指产品、设备、服务,或者环境是为残疾人士设计的。无障碍设计的概念意味着与一个人的辅助技术(例如, 脑屏幕阅读器)相兼容, 确保直接访问(即独立)和“间接访问”。
无障碍设计可以理解为“能够访问”,并对一个系统或实体是有利的,其侧重于使身体残障,或有特殊需要,或要依赖辅助技术的人群能够访问 Web。然后,研究和开发无障碍设计对每个人都带来了好处。
无障碍设计不应该和可用性混淆。大多数情况下,可用性是指产品(如:设备、服务、或者环境)能在特定的环境下被特定的用户使用,来高效地实现制定目标。
无障碍设计和通用性设计是息息相关的。通用型设计是指产品的创造过程中,产品对人们是可用的,并尽可能最大范围覆盖各能力范围内的人群和各种情形下的操作,即对所有人是可访问的(无论他们访问 Web 是否有障碍)。
2. 富互联网应用
开发人员使用HTML、CSS和JavaScript创建富互联网应用程序时,往往把残疾人士抛在脑后,因为这些应用程序无法提供被辅助技术理解所需的语义信息。可悲的是,直到现在这种情况并没有多少改变,其实我们可以扭转这种局面。WAI-ARIA 能够提供足够的语义,以确保富互联网应用是可以理解的,并且现在已经得到相对较好的支持。
3. ARIA是什么?
ARIA 是 Accessible Rich Internet Applications 的缩写。它是W3C的 Web无障碍推进组织(Web Accessibility Initiative / WAI),在 2014年3月20日 发布的 可访问富互联网应用 实现指南。
WAI-ARIA 是一个为残疾人士等提供无障碍访问动态、可交互Web内容的技术规范。在 WAI-ARIA概述 中对 WAI-ARIA 及其他支持文档进行了介绍。
简单点描述:
- ARIA 是 W3C 的一个独立规范,帮助 Web 应用程序和 Web 页面变得更具可访问性;
- ARIA 主要是为了提升网页的可用性,网页对残疾人士的无障碍化;
- HTML5 已经开始使用 ARIA,并且 W3C 发布的很多其他标准也开始使用 ARIA;
- ARIA 是对 HTML语义化 的补充。它具备比现有的 HTML 元素和属性更完善的表达能力,并让你页面中元素的关系和含义更明确;
- ARIA 规范为浏览器和解析 HTML 文档的辅助性技术提供了一种可以让人们以多种方式访问和使用 Web 的标准方法;
4. 如何使用ARIA?
应用于HTML的 ARIA 有两部分组成:role
(角色)和带 aria-
前缀的属性,其作用:
role
(角色)标识了一个元素的作用aria-
属性描述了与之有关的事物(特征)及其是什么样的(状态)
5. HTML中的ARIA
ARIA 在 HTML 中使用有其自己的规范,并不是说在 HTML 中使用了 ARIA,Web页面就无障碍化了,就提高了可访问性了。言外之意,ARIA 没有用好,反而会把你带到另一个坑中,使用你的页面可访问性更差。
有关于HTML中 ARIA 的文档可以 点击这里阅读。
5.1. ARIA在HTML中的使用
5.1.1. ARIA使用规则一
如果你使用的元素 HTML5 具有语义化,应该使用这些元素,而不应该重新定义一个添加 ARIA 的角色、状态或属性的元素。
那么什么时候可用和不可用 ARIA 呢?
- 在 HTML(HTML5)元素特性不管支持或不支持,只要不具语义化,就可以使用 ARIA;
- 排除视觉设计约束使用一个特定的元素,但不能是样式上所需的元素;
- 目前尚不支持的 HTML 特性;
5.1.2. ARIA使用规则二
不改变语义,除非你真的需要使用。例如,开发者想创建一个标题,而且它是一个按钮。
不要这样做:
1 | <h1 role="button">标题按钮</h1> |
建议这样做:
1 | <h1><button>标题按钮</button></h1> |
或者说,你不使用正确的元素,但你可以这样做:
1 | <h1><span role="button">标题按钮</span></h1> |
如果使用一个非交互的元素做为一个交互的元素,那么开发人员必须使用 ARIA 添加语义和使用适当的脚本增加交互行为。
5.1.3. ARIA使用规则三
所有的 ARIA 制作控件都必须具有键盘(keyboard)事件。
如果创建一个组件(widget),用户可以点击、拖放、滑动或滚动,用户使用键盘能定位到创建好的组件部件上,并且执行相应的操作动作。
例如,如果使用 role=button
必须能够接收到焦点和用户能够使用键盘激活相应动作,比如Win操作系统上的enter
和 iOS 系统上的 return
或者键盘的空格键 space
。
5.1.4. ARIA使用规则四
不建议在可获取焦点元素 focusable
使用ARIA的角色:role=presentation
或 aria-hidden="true"
。
可获取焦点元素上使用ARIA这两规则,会导致一些用户无法获取元素焦点。
不要这样做:
1 | <button role="presentation">按下我,按下我</button> |
也不要这样做:
1 | <button aria-hidden="true">按下我,按下我</button> |
如果说一个交互元素无法看到或者不能与之交互,那么可以尝试使用 aria-hidden
,例如:
1 | button {visibility:hidden} |
如果一个交互元素使用 display:none;
来隐藏,那么它对应的 可访问性 也将一并被删除,如此一来,在可交互元素上使用aria-hidden="true"
就没有必要了。
5.1.5. ARIA使用规则五
所有交互元素都必须有一个 可访问的名称。
当可交互元素的可访问性API的可访问名属性只有一个值时,那么可交互元素就只有一个可访问的名称。
比如下面的示例,input type="text"
有一个可见的 <label></label>
标签,但它并没有可访问的名称:
1 | user name <input type="text"> |
或者:
1 | <label>user name</label> |
此时 MSAA (Microsoft Active Accessibility )控制器的 accName
属性是空的:
相比之下,下面示例中 input type="text"
有一个可见的 <label></label>
标签并且包含一个可访问性名称:
1 | <label>user name <input type="text"></label> |
或者:
1 | <label for="uname">user name</label><input type="text" id="uname"> |
此时MSAA (Microsoft Active Accessibility )控制器的accName
属性值是 user name
:
另外 label
标签元素是不能被用来给自定义控件提供一个访问性名称,除非 label
引用了HTML的 labelable
元素。
1 | <!-- HTML input element with combox role --> |
MSAA(Microsoft Active Accessibility )控制器的accName
属性值是 user name
:
除此之外,使用非HTML labelable
元素来模拟控件,不管给其分配什么角色( role
)都不会是HTML的 labelable
元素,比如下面的 div
元素:
1 | <!-- HTML div element with combox role --> |
MSAA(Microsoft Active Accessibility )控制器的 accName
属性值是空的:
6. ARIA 的角色 role
下面的表格详细的示意了 HTML 中元素如何使用ARIA的角色 role
,以及对应的含意:
上表中并没有列出所有 ARIA 的角色,当然并不是所有的HTML元素都具有对应的 ARIA 的 role
,下表列出了常用的标签元素对应的 ARIA 的role,仅供参考:
6.1. 注释一
通过一个示例来看看 role="presentation"
运用前后对HTML元素可访问树对比。
1 | <table> |
在上面的代码上添加 role="presentation"
:
1 | <table role="presentation"> |
table
元素可访问树前后对比示意图如下:
前面也说到过,并不是在HTML中添加ARIA角色对屏幕阅读器就是好的,特别是对于一些默认带有交互功能的元素中,添加ARIA就是浪费时间,比如:
1 | <button role="button">按我</button> |
当然在HTML5的一些特定元素上,ARIA的 role
和自带的语义化元素是可以重叠的,如下表所示:
HTML5元素 | ARIA Role |
---|---|
<header> | role="banner" |
<nav> | role="navigation" |
<main> | role="main" |
<footer> | role="contentinfo" |
<aside> | role="complementary" |
<section> | role="region" |
<article> | role="article" |
<form> | role="form" |
搜索表单 | role="search" |
假设有一个简单的HTML5页面:
1 | <header role="banner"> |
如果你不想使用HTML5的元素,可以使用 div
来替代:
1 | <div role="banner"> |
如图所示:
7. ARIA的属性
下表介绍了ARIA相关属性的使用方法。
8. ARAI状态
下表介绍了ARIA相关状态的使用方法。
属性状态 | 属性值 | HTML示意 | 说明 |
---|---|---|---|
aria-checked | 字符串。表示检查的状态。true表示元素被选择;false表示元素未被选择;mixed表示元素同时有选择和为选择状态。 | <li role="checkbox" aria-checked="mixed" tabindex="0"></li> | 该属性用来表明用户是否选择了某些项,类似于input type="checkbox"中的checked |
aria-disabled | 字符串。表禁用状态,true表示当前是非激活状态;false表示清除非激活状态。 | <div role="button" tabindex="0" aria-pressed="false" aria-disabled="false"></div> | 对应单复选框等控件的disabled属性,一般用在自定义模拟控件中 |
aria-expanded | 字符串。表示展开状态。默认为undefined, 表示当前展开状态未知。其它可选值:true表示元素是展开的;false表示元素不是展开的。 | ||
aria-hidden | 字符串。可选值为true和false, true表示元素隐藏(不可见),false表示元素可见。 | <div aria-hidden="false"></div> | 该属性使用非常普遍。几乎所有涉及到显示与隐藏这类交互或没有交互的地方都可以应用该属性。左边的示例HTML代码来自进度条进度值显示的div, 当前aria-hidden为false, 表示该进度值是可见的。 |
aria-invalid | 字符串。表示元素值是否错误的。默认为false, 表示是OK的,如果为true, 则表示值验证不通过。 | <input type="text" size="3" aria-labelledby="valid" aria-invalid="false" /> | |
aria-pressed | 字符串。表示按下的状态,可选值有:true, false, mixed, undfined。默认为undfined, 表示按下状态未知;true表示元素往下(按钮按下);false表示元素抬起;mixed表示元素同时有按下和没有按下的状态。 | <div role="button" tabindex="0" aria-pressed="false" aria-disabled="false"></div> | 左边HTML表示按钮已经按下,同时处于禁用状态。 |
aria-selected | 字符串。表示选择状态。可选值有:true, false, undefined。 默认为undefined,表示元素选择状态未知。true表示元素已选择;false表示未被选中。 |
8.1. 有关于Roles、States 和 Properties相关规范文档(W3C规范)
9. 开发最佳实践
开发一个可访问的 Web 应用不仅需要工具的支持,浏览器的支持,还需要开发人员遵循一定的规范提供对应的元素信息,才能达到最终的目的。下面着重介绍一些开发中的最佳实践。
9.1. 关于 Image
图片或者动画均需提供 alt
信息,使得读屏软件可以将图片动画的内容清楚的读出来。如图所示:
对应的 HTML 如下:
1 | <img src="cat.gif" alt="Image about cat" /> |
对于某些用于装饰性的图片,则需设置 alt
为空,使得读屏软件可以忽略此元素。如图用于装饰页头的图片,实际并没有传递有价值的信息。
对应的 HTML 如下:
1 | <img src="ring.gif" alt="" /> |
必须设置一个空 alt
属性的目的是为了能通过 Webking
的检查,并且使得读屏软件能够忽略此元素。
对于图表文件,alt
属性的设置则需要简明扼要的表达出图表的信息,并不用把里面的细节都详细得描述出来。例如下面的图 alt
信息设置为销售额从 1996 年到 2004 年间持续稳定增长,从 400 万增长到了 1600 万。并不需要把每一年的增长额都详细得描述出来。
对于放在链接里面的图片,如果已经有文字的说明,alt
也设置为空,这样避免读屏软件重复同样的内容。如下面的 HTML:
1 | <a href=”http://apple.com/iphone/”> |
a
的内容已经指明了这是个苹果手机,img
的 alt
属性就没必要再设置一次了。否则读屏软件会连续读两次重复的内容,引起混乱。
CSS
将样式跟结构分离,使得 HTML 代码结构清晰。很多装饰性的图片也都放在 CSS 里面来加载,带来的一个问题就是在 CSS 里面的图片在高对比度模式下都无法显示。如果这个图片并不仅仅是装饰性的,还可以触发功能,那就需要从 CSS 里面拿出来,当成一个独立的 img
或者 input
元素。例如下面的一个提示保存的图片:
写在 CSS 里面的做法是:
1 | <div class=” save_button” ></div> |
这样当用户切换到高对比度模式,这个图片就是一片空白,用户无法再去点击保存。修改如下:
1 | <img src=“images/save_button.png” alt=“save”/> |
在一个图片列表里面选中某个图片,区别选中去否我们通常的做法是用边框的颜色来标识。如下图,选中的图片边框为蓝色
1 | .selectedIcon{ |
但这样的一个实现实际上违反了可访问检查列表中的一项:不能仅仅通过颜色来区分不同的元素,因为在高对比度下只有黑色或白色,这样的区分在高对比度下是没有任何作用的。我们很容易想到的一种办法就是只有选中的时候才加边框,未选中时则没有边框,这样就可以区分出来了。修改如下:
1 | .selectedIcon{ |
这样引起的问题是,图片的布局在选中的时候会浮动,增加了 5px 的边框,看起来效果就很差。那么怎么保证布局又满足可访问性的要求呢? 可以在上面 CSS 的基础上通过 padding
属性使得布局正确:
1 | .selectedIcon { |
这样保证整体的边界都是 5px
,在高对比度下的效果如图所示:
9.2. 关于 Table
Table
分为两类:一类是做布局的 table
,一类是数据 table
。
- 对于布局用的
table
,读屏软件没必要知道这是一个表,可以通过设置role=presentation
使JAWS
忽略这个表,只关注里面的内容。 - 对于数据表格,则需要设置
caption
属性,说明整个表是用来做什么的,使得 JAWS 可以告诉用户这个表的作用。 - 对于每一个单元内的数据,还应该通过 th 属性使得 JAWS 能识别这个数据的表头是什么。对于复杂表,可以通过 id 和 header 属性来标识。如图所示:
以第一行的数字 5 为例,正常人可以很容易得看出 5 指的是一年级 Mr.Henry 老师这个班的男生有 5 个,但当 JAWS 面对这个数字 5 的时候,怎么能识别出来呢?通过 header 来标识表头,header 的值就指向对应表头的 id。对应的 HTML 如下:
1 | <tr> |
9.3. 关于 Form
form
元素需要关联一个 label
元素,所有的 button
都已经有了一个隐含的 label
,所以不再需要显示关联。对于 input
select
checkbox
radio
button
则都需要显示一个 label
元素。这样 JAWS 在面对这个表单元素的时候才能告诉用户这个表单的作用。例如下面的 input
, JAWS 会告诉用户这个是需要输入名字的一个输入框。当 label
属性不方便使用的时候,还可以通过 title
属性达到相同的效果,也可以满足 Webking 检查的需要。下面的两种写法都可以。但前提是 name 不需要被显示出来。当 title
和 label
都设置的时候 title
会被 JAWS 忽略。
1 | <label for="name1">Name:</label> |
当一个表单元素如果前后都需要描述的时候,label
就显得力不从心了。ARIA 规范的出现解决了这一问题。aria-labelledby
属性可以设置多个值,说明这个表单元素是被那些值所描述的, aria-describedby
属性则更详细的扩展了这个描述。如下图所示:
当 JAWS 把焦点放在 10 上的时候,会告诉用户 10 表示的是 10 分钟刷新一次。对应的 HTML 代码如下所示。aria-required的属性标识这个元素是必须的,JAWS 识别此元素并告知用户必须输入此元素。我们可以看到中间的 input 元素被多个元素来描述(aria-labelledby 中的几个 id 值),这样 JAWS 就能够识别这个标签,并且按照这个标签的顺序读出前后的 label, 并且提示用户如果还有更详细的描述以及如何获取这个更详细的描述。当用户需要时,aria-describedby 所对应的元素信息就会被读出来。增强了视力有障碍人士与普通人了解内容的一致性。
1 | <div> |
9.4. 关于 Tabindex
与获取焦点的顺序
Tabindex
属性的使用可以使得原本无法取得焦点的元素获取焦点。目的是为了使用户可以用键盘访问任何可以用鼠标访问的元素。我们知道,使用 Tab
键可以按文档顺序 tab
到所有可以获取焦点的元素。tabindex
可以设置为 -1
、0
或者是任何自然数。
tabindex = 0
就使原本无法获取焦点的元素可以在用户tab
的时候获取焦点,并且按照文档顺序排列。tabindex = -1
使得元素可以获取焦点,但当用户用 tab 键访问的时候并不出现在 tab 的列表里面。可以方便的通过 Javascript 设置上下左右键的响应事件。非常有利于应用小部件(widget)内部的键盘访问。tabindex
设置为大于 0 的数字则可以控制用户 Tab 时候的顺序,一般很少用。
当用户使用 Tab 键浏览页面时,元素获取焦点的顺序是按照 HTML 代码里面元素出现的顺序排列的,有时跟实际看到的页面顺序并不一致。例如图 所示的页面:
按照页面顺序,tab 的顺序为自左向右,可实际上操作的时候却发现“search all”出现在了“go to edit”的前面。对应的 HTML 代码如下所示:
1 | <!-- 页面获取 focus 的顺序 --> |
原来是通过 float:right
达到了布局上的效果,实际文档顺序确实是 search all
在前面的。所以为了不引起混淆,最后能保持代码的顺序与实际呈现出来的页面上的顺序一致。可以修改上面的代码为,如下所示:
1 | <!-- 页面获取 focus 的顺序 调整后 --> |
9.5. 关于隐藏的内容
隐藏的内容分为两种:
- 一种是为了布局的需要,在条件满足的情况下才会显示出来;
- 另一种是只给读屏软件读的内容。
有时候我们为了使读屏软件更准确的读取信息,会提供一些额外的描述来达到此效果,但为了不给正常用户带来困扰,这些内容对正常用户来说是隐藏起来的。隐藏内容我们通常用 display:none
或者 visibility:hidden
来表示,但读屏软件同样也会忽略这类内容。那如何隐藏内容又能使读屏软件读出来呢?
另外一种隐藏内容的方式是使用绝对定位使得内容不出现在当前屏幕上,如:{position:absolute;top:-30000px;}
所以在选择使用哪种方式隐藏内容时就需要慎重考虑,display:none
visibility:hidden
对任何人都是隐藏的,如果想只给读屏软件读到就需要使用上面的绝对定位方式。例如在下图所示的菜单的选中项上加上如下的 css:
1 | <!-- 只给读屏软件读的内容 --> |
这样当用户使用 JAWS 浏览每一个菜单项时,在选中项上就能听到哪一项是当前的所选中。
9.6. 高对比度模式的小技巧
系统切换到高对比度模式,只有黑白两色,很多在正常模式下依靠颜色来区分的(如界面边界)都无法辨识了,写在 CSS 里面的很多图片也都无法显示出来。此时就需要在高对比度下增加边界或者另外 DOM 节点来显示同样的内容。Dojo 的 WAIState Api
提供了一种方式来判断系统是否处于高对比模式,如果是则在 body
上增加 dijit_a11y
的一个 CSS。这样可以在正常模式下显示一个 DOM 节点在高对比度下显示另外一个 DOM 节点,从而方便的区分。如下图所展示的正常模式与高对比模式下的对比:
正常模式下如左图所示,子菜单通过一个图片标识,但这个图片是在 CSS
里面设置的,切换到高对比度模式即无法显示出来。此时,我们增加一个在高对比度模式下才显示出来的节点,达到如图右所示的效果,在高对比度下显示一个 + 号。代码如下所示,在高对比模式下,dijit_a11y
加在 body
上,dijitMenuExpandA11y
所对应的 DOM 即应用右面的 CSS 得以显示出来。
1 | <!-- 正常模式与高对比模式显示不同的 Dom 节点 --> |
10. ARIA Widgets
10.1. Label
ARIA前
1 | <h2 class="offscreen">System Folders</h2> |
ARIA后
1 | <ul role="listbox" aria-label="System Folders"> |
10.2. Alert Dialog
1 | <div role="alertdialog" aria-labelledby="hd" aria-describedby="msg"> |
10.3. headings
1 | <p class="heading1" role="heading" aria-level="1" >Heading 1 |
10.4. list/listitem
1 | <div role="list"> |
10.5. button
1 | <span tabindex="0" role="button" class="...">Button</span> |
10.6. toggle button
1 | <span tabindex="0" role="button" aria-pressed="false" class="...">Option</span> |
10.7. checkbox
1 | <span tabindex="0" role="checkbox" aria-checked="false" class="...">Option</span> |
10.8. radio
1 | <span tabindex="-1" role="radio" aria-checked="false" class="...">Yes</span> |
10.9. link
1 | <span tabindex="0" role="link" onclick="document.location(...)">link</span> |
10.10. tooltip
1 | <span tabindex="0" onmouseover="..." onfocus="..." aria-describedby="tooltip" >...</span> |
10.11. status bar
1 | <span role="status" >some form of status bar message...</span> |
10.12. alert
1 | <span role="alert" >an alert message (no user interaction)</span> |
10.13. progress bar
1 | <div tabindex="0" aria-valuemin="0" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="..." aria-valuenow="40" aria-valuetext="40% complete"> |
10.14. menu
1 | <div role="menu"> |
10.15. Menubar
1 | <div role="menubar"> |
10.16. listbox
1 | <div role="listbox" aria-activedescendant="opt2" tabindex="0"> |
10.17. combobox
1 | <!-- similar to <select> --> |
10.18. tree
1 | <!-- list with selectable items, expand/collapse, nesting --> |
10.19. grid
1 | <!-- interactive table/spreadsheet --> |
11. Bootstrap ARIA示例
11.1. Glyphicons
1 | <button type="button" class="btn btn-default" aria-label="Left Align"> |
11.2. Dropdowns
1 | <div class="dropdown"> |
11.3. Divider
1 | <ul class="dropdown-menu" aria-labelledby="dropdownMenuDivider"> |
11.4. Button Groups
1 | <div class="btn-group" role="group" aria-label="First group"> |
11.5. Button dropdowns
1 | <!-- Single button --> |
11.6. Input groups
1 | <div class="input-group"> |
11.7. Pagination
1 | <nav> |
11.8. Alert
1 | <div class="alert alert-warning alert-dismissible" role="alert"> |
11.9. Progress bars
1 | <div class="progress"> |
11.10. dialog
1 | <div class="modal fade" tabindex="-1" role="dialog"> |
11.11. tab
1 | <div> |
11.12. tooltip
1 | <!-- HTML to write --> |
11.13. Collapse
1 | <a class="btn btn-primary" role="button" data-toggle="collapse" href="#collapseExample" aria-expanded="false" aria-controls="collapseExample"> |