现代 CSS

A11Y 101:WAI-ARIA初探

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

随着互联网技术不断的革新,Web页面或应用不再局限于静态地向用户呈现文本或图片等内容。现在的Web页面或应用是一个 富应用,是一种具有近似于传统桌面应用软件系统功能和特性的网络应用系统。这也就是说,用户和Web应用的交互变得更丰富更复杂。实现这些带有富交互的Web应用仅依靠HTML和CSS是无法实现了,换句话说,我们很多时候会使用一些非语义的HTML和动态的JavaScript脚本来构建,更新复杂的UI控制。Web应用变得更丰富的同时,可访问性的难度也就相应的更复杂了。

如果你阅读过该系列的前几篇文章,比如编写可访问性的Web应用在HTMLCSS需要注意哪些问题?那么你就或多或少知道在编写HTML和CSS时要注意的细节,以及这些细节怎么让一个Web应用更具可访问性。不过,话又说回来,构建一个可访问的Web应用并不是一件易事,其所涉及到的知识点非常的多,非常的细。比如,面对一个富应用,富交互的Web应用,仅仅依赖于HTML和CSS是无法知道他们所处的状态。那么这个时候就需要JavaScript的脚本来增强这方面的能力。而且很多时候会使用非语义化的HTML标签和动态的JavaScript脚本来构建或更新复杂UI控件,那么这些控件在可访问性方面就会更差。

不过幸运的是,WAI-ARIA技术的出现,让可访问性变得没有那么的难。因为WAI-ARIA技术可以通过浏览器和一些辅助技术来帮助我们进一步地识别以及实现语义化,除了帮助我们解决语义化的问题之外,还可以让用户知道发生了什么。

在接下来的内容中,将和大家一起初步的探讨WAI-ARIA技术给可访问性带来的变化。

WAI-ARIA是什么?

WAI-ARIAAccessible Rich Internet Applications的一种简称,很多时候也被称为ARIA(即Accessible Rich Internet Applications首字母缩写)。它是W3C的Web无障碍推进组织(Web Accessibility Initiative / WAI)在2014年3月20日发布的可访问富互联应用实现指南

W3C是这样定义WAI-ARIA的:

WAI-ARIA provides a framework for adding attributes to identify features for user interaction, how they relate to each other, and their current state. —— 来自于W3C

大致的意思是:WAI-ARIA提供了一个框架,用于添加属性来识别用户交互的特性,它们之间的关系以及它们当前状态

而WAI是这样定义的:

A way to make Web content and Web applications more accessible to people with disabilities. It especially helps with dynamic content and advanced user interface controls developed with Ajax, HTML, JavaScript, and related technologies. —— 来自于WAI

大致意思是:WAI-ARIA是一种使Web内容和Web应用程序更容易被残障人士访问的方法。它特别有助于使用Ajax、HTML、JavaScript和相关技术开发动态内容和高级界面控件

特别声明:可访问性(或无障碍)服务的用户群体不仅仅局限于残障人士,换句话说,WAI-ARIA也不仅是让残障人士更好的访问Web,而是应该让访问Web有障碍的用户群体更好的访问Web应用。有关于这方面更多的讨论,可以阅读《A11Y 101:构建可访问性应用的2W1H》一文。

简单地说,ARIA定义了一组属性来帮助修改不正确的标记并弥补HTML中的空白,从而为使用辅助技术(AT)的用户提供更方便的体验。正确地将ARAI合并到你的代码中可以确保辅助技术设备用户拥有使用你的网站或应用程序所有的信息。换句话说,构建一个可访问性的Web网站或Web应用就在原有的HTML、CSS和JavaScript基础上新增了ARIA

ARIA技术方便你将有关元素的名称(Name)角色(Role)状态(State)属性(Properties)与其他元素的关系的信息传递给辅助技术。而且,ARIA对于辅助技术(比如屏幕阅读器)用户来说很像CSS,因为它纯粹是描述性的。它不会导致浏览器改变底层元素的外观或功能。比如下面这个示例:

<!-- 使用角色(role)定义了div元素是一个button -->
<div role="button">Here is a snazzy button</div>

<!-- 使用属性(Properties)描述了div元素的特征或关系 -->
<div role="button" aria-describedby="some-other-element">Here is a snazzy button</div> 
<div id="some-other-element">This page will self destruct in 10 seconds.</div>

<!-- 使用状态(State)定义与元素相关的当前条件或数据值 -->
<div role="button" aria-describedby="some-other-element" aria-pressed="false">Here is a snazzy button</div> 
<div id="some-other-element">This page will self destruct in 10 seconds.</div>

不难发现,上面的HTML代码中有一些新的属性,比如rolearia-describedbyaria-pressed等。你可能现在还不知道这些标签属性有何用,怎么用?其实也不必着急,随着后面的学习,你会明白他们具体的含义和作用。在这里,你只需要知道它们是ARIA中的相关特性即可。

从上面的代码中你可能发现了,ARIA通过更改和补充标准DOM的属性来发挥作用,这些属性可以修改元素转换成无障碍树的方式:

用一个小示例简单地向大家演示AOM树(Accessibility Object Tree)的形成过程:

在浏览器渲染地过程中,可访问树(AOM Tree)和 DOM树是并行结构。粗略地说,可访问树是DOM树的子集。它包括用户代理的用户接口对象和文档的对象。可访问对象是在可访问树中为每个应该暴露给辅助技术的DOM元素创建的,因为它可能触发一个可访问事件,或者因为它有一个需要暴露的属性、关系和特性。通常情况下,如果某些内容可以被删除,那么它就会被删除,这是出于性能和简单性的考虑。例如,只有样式更改而没有语义的<span>可能无法获得自己的可访问对象,但是样式更改将通过其他方式来处理。

有关于AOM树,我们在后续会专门和大家一起探讨。

所以说,大家不必担心ARIA会更改你的Web页面功能或整体外观(除非你的CSS中使用属性选择器用来控制ARIA相关的属性,比如[role="button"])。如果我们了解了AOM树的话,可以清楚的地知道ARIA可以看作是HTML和ATs之间的另一层理解。这也意味着,除了ATs(以及使用它们的人),没有人会注意到有ARIA的网页或应用程序与没有ARIA的网页或应用程序之间会存在任何的区别。

当然,对于开发者而言,如果借助ARIA技术来增强可访问性的构建,那么有ARIA和无ARIA的区别还是很大的。最起码,你的代码会越来越复杂。因此了解ARIA的角色、属性和状态的基本规则可以帮助你在HTML标签中如何正确的使用ARIA技术。

时至今日,WAI-ARIA规范主要有两个版本,一个是1.0版本,另一个是1.1版本,其中1.1版本只是包含了1.0版本的一些更改

在2.0版本中将会涵盖大多数的更改。

有关于WAI-ARIA当前的情况和发展更详细的介绍,可以点击这里进行了解

为什么要使用WAI-ARIA

随着Web应用变得越来越复杂和动态化,一堆全新的Web可访问性问题和特性也接踵而至。

例如,HTML5提出了几种语义化标签用于定义常规页面的特性(比如headermainfooternav等)。在这些标签可用之前,我们一般是在div元素上命名带有语义化的类名(class)或ID名称。比如<div class="header"></div>。但是这种实践是问题丛生的,因为没有简单的方法可以轻松地用可编程的方法找到特定页面功能,例如主导航。

最早的解决方案是加一个或多个隐藏的链接来跳转到我们想要的位置,例如:

<div id="skip-link">
    <a href="#main-content" class="visible-hidden">跳转到主要内容</a>
</div>

<div id="main-content"></div>

.visible-hidden {
    clip: rect(1px, 1px, 1px, 1px);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}

.visible-hidden:focus {
    clip: auto;
    height: auto;
    overflow: auto;
    position: absolute;
    width: auto;
}

但这仍然是不准确的,而且只能在屏幕阅读器开启页面顶部读取时使用。

另一个例子:应用开始支持一些复杂的类型(<input />)输入,像是日期选择器可选择日期,或是范围选择器可以用滑块选择值,HTML5 提供了以下的类型:

<input type="date" />
<input type="range" />

他对于跨浏览器的支持并不好,而且他的样式修改也很麻烦,这使得他在网页的集成设计上难以有所用途。我们常常会用JavaScript 库来做这事,所以会用一系列嵌套的div 或者带有classtable元素,然后用CSS来制定样式,JavaScript 来控制行为。

这样的问题是视觉上这样做事可行的,但屏幕阅读器对此会感到一无所知,他只能告诉用户她看到一堆复杂结构的元素,且毫无语义可言。

这个时候我们就需要依赖于ARIA技术,在DOM元素上添加适当的ARIA属性,让屏幕阅读器也能感知到这些UI控件是什么?

WAI-ARIA可以做什么?

通过前面的学习,我们知道WAI-ARIA是W3C编写的一套规范,定义了一组可用于其他元素的HTML特性(Attributes),用于提供额外的语义化以及改善缺乏的可访问性。比如前面示例中的代码:

<div role="button">回到首页</div>

div标签是属性无语义化的一个HTML标签,通过ARIA技术,即给div标签加上role(角色),这样一来一些辅助技术(比如屏幕阅读器)就知道这个div不是一个普通的标签,而一个具有按钮角色的标签。

正如上面示例所示,ARIA就是这么简单易于。虽然ARIA是一套独立的规范,但对于一个Web页面或Web应用程序而言,ARIA始终离不开HTML,哪怕是通过JavaScript动态来控制ARIA,也最终附加到HTML标签元素上。如果不想思考太多,可以简单地理解成:

ARIA所有特性都是HTML的附加属性(Attributes)

ARIA整个规范中有三个主要的特性:角色(Role)状态(State)属性(Property)

这三个主要特性涉及较多的内容,后续我们还会分章节阐述,感兴趣的同学可以持续关注后续相关更新。在这篇文章中只会简单的介绍这三个特性。

进入WAI-ARIA的世界

我身边很多小伙伴都喜欢玩游戏,比如王子荣耀,如果你和我一样不太熟悉这些游戏也不要紧,没吃过猪肉总看过猪跑吧。在游戏中有很多人物角色的扮演:

这些角色在奇幻游戏世界中进行战斗和完成相应任务。虽然我自己并不太喜欢玩这些游戏,但我注意到游戏中的角色扮演的方式与Web应用程序中的HTML元素的行为方式有着相似之处。因此,我将使用这个角色扮演来比喻ARIA中的角色、属性和状态更有助于大家理解。

角色(Roles)

游戏中的每个角色都有一个“角色表”,列表所扮演角色的重要特征。比如王子荣耀中的“西施”所具备的重要特征如下:

在HTML中元素可能会有属性,属性会有名称,名称可能是一串字符串,比如id的属性名称footer。在HTML中的id这样的属性是唯一的。但是字符比工作表中包含的信息要多得多。例如,在游戏中的角色通常属于一个或一个“种族”。该角色可能会有很多种替身(比如“精灵”、“矮人”和“巨魔”,这些可能在王子荣耀游戏中没有,因为我不懂游戏,但性质是类似的)。游戏中这些类似于HTML元素类型:具有共同特征的参与者的广泛分组,是不是有点像HTML元素周期表:

在ARIA中,角色属性覆盖了元素类型,就像游戏中“矮人”。在前面我们的示例代码中看到过role="button",其实就是把div充当了button的角色:

ARIA中的角色就像游戏中扮演角色中的“种族”一样,是我们可能感兴趣的角色身份的一部分。我们可能期望“矮人”们强壮并且擅长构造机器,就好比我们期望在非<button>元素上具有特征和行为一样。通过role="button"让一个实际上不是<button>的元素在辅助技术(比如屏幕阅读器)上可以将其识别成一个<button>,并且让其具备相应的特征和行为。

如果用术语来描述的话,ARIA中的角色是用于定义用户界面(UI)元素的类型,一旦为元素设置了角色,它就不会更改。ARIA规范中明确的描述和罗列了所有角色相关的信息

为了支持当前用户场景,ARIA规范将定义用户界面小部件(比如滑块,树控件等)和定义页面结构(比如节、导航等)的角色分类。注意,一些辅助技术为带朋角色应用程序或文档的区域提供了特殊的交互模式。在ARIA中所有角色的分类可以用下图来描述:

可能很多角色到目前为止不一定能全部用上,我们可以像HTML元素周期表一样,整理一份类似ARIA角色的周期表,它可能像下面这样:

具体的ARIA角色可以分为以下几种类型:

抽象的角色(Abstract Roles)

抽象的角色是所有其他角色的基础,但是浏览器使用抽象角色不应该在代码中使用。相反,它们用于在上下文中赋予角色意义,并帮助添加新角色。在ARIA中被列表抽象角色的主要有:commandcompositeinputlandmarkrangeroletypesectionsectionheadselectstructurewidgetwindow

部件的角色(Widget Roles)

当HTML没有定义元素时,部件角色为元素和用户界面(UI)添加语义,无论大小。根据W3C的 ARIA 5.3.2 Widget Roles所描述,可以用多个独立的UI小部件组合成更大的小部件,而复合UI小部件充当管理其他包含小部件的容器。

独立的小部件主要有:buttoncheckboxgridcelllinkmenuitemmenuitemcheckboxmenuitemradiooptionprogressbarradioscrollbarsearchboxseparatorsliderspinbuttonswitchtabtabpaneltextboxtreeitem

以下角色充当复合(组合)用户界面小组件。这些角色通常充当管理其他包含小部件(上面所列的小部件)的容器:comboboxgridlistboxmenumenubarradiogrouptablisttreetreegrid

文档结构角色(Documeent Structure Roles)

文档结构角色通常不是交互式的,而是为页面中的各个部分提供描述。常见的文档结构角色主要由有:applicationarticlecellcolumnheaderdefinitiondirectorydocumentfeedfiguregroupheadingimglistlistitemmathnonenotepresentationrowrowgrouprowheaderseparatortabletermtoolbartooltip

如果在构建Web页面或Web应用中使用了文档结构角色的话。ATs的用户在浏览页面时会使用文档结构角色来标识内容,这有助于为他们提供所接收内容的上下文。虽然HTML5本身可能提供了很多这样的上下文,但是HTML5有时缺少对屏幕阅读器支持。因此,最可靠的方式HTML5标签元素和ARIA的文档结构角色结合起来使用

地标角色(Landmark Roles)

为了更好容易的导航,ARIA中的地标角色(Landmark Roles)可以识别页面内的每一部分内容。常见的地标角色主要有:bannercomplementarycontentinfoformmainnavigationregionsearch

如果你在HTML的标签元素上使用了地标角色,那么ATs的用户在浏览页面时会使用地标角色来进行导航。例如,屏幕阅读器将在页面上宣布每个地标角色的开始和结束。

如果你足够仔细的话,你可能发现了ARIA中的地标角色和HTML5的部分标签元素是稳合的:

对应关系如下面表格所示:

ARIA Landmark Roles HTML5 Element
role="banner" <header>
role="navigation" <nav>
role="main" <main>
role="contentinfo" <footer>
role="complementary" <aside>
role="search"  
role="form" <form>

为了让ATs用户具有一个更好的体验,同样建议在上述的HTML5标签元素上使用相对应的地标角色,但并不是对所有的<main><header><nav><footer><aside>标签上都运用地标角色,应该在页面主结构布局上使用才更合理。

Live区域角色(Live Region Roles)

使用JavaScript,可以动态更改页面的某些部分,而无需重新加载整个页面。例如,可以动态更新搜索结果列表,或者显示不需要用户交互的警告或通知。虽然这些更改对于能够看到页面的用户来说通常是显而易见的,但是对于有障碍用户来说,它们可能并不明显。ARIA Live区域填补了这个空白,并提供了一种以编程方式显现动态内容更改的方法,这种方式可以由辅助技术提供提示。

在ARIA中Live区域的角色主要有alertlogmarqueestatustimer组成。

窗口角色(Window Roles)

ARIA中的窗口角色可以用来充当浏览器或应用程序中的窗口。常角的角色主要有alertdialogdialog

我想你看到上面有关于ARIA的角色(甚至上看到最初的角色图)就有点心虚了吧。其实我和你们是一样的,看上去一大框,不知道如何下手,事实上如果你按照ARIA规范中角色的定义列表一个一个去看,会发现,原来也没有想象的那么复杂。如果你对ARIA中角色相关的适应和使用感兴趣的话,我们随着后续的学习可以和大家一起分享。如果您先我一步学习相关的知识或者你早已具备这方面的知识,欢迎在下面的评论中与我们一起共享。

属性(Properties)

ARIA的意义在于它能识别元素,而不仅仅是一种简化的分类。将ARIA的角色和元素(以及元素的自已属性)结合起来使用能让元素得到更好的识别。比如一个表单会列出一组角色的特征,这些特征在某种程度上被游戏识别为具有某种货币和重要性。例如,你可能是一个精灵,但是你有特殊的能力去施展某些魔法。在HTML中的元素可以以同样的方式来设计。比如一个<a>元素,它是为了具有子菜单的属性而特别设计的。这将通过aria-haspopup="true"属性标识到可访问性这一层。

ARIA中属性有一份详细的描述文档。这些属性中有些是全局的(任何元素都可以使用),有些是局部性的,只能给特定的上下文和元素使用。这也好比游戏中的“矮人”就通常就用不了长弓这样的设备,而“精灵”就没有这样的限制。用到实际开发中,像aria-label就是一个全局属性,而aria-required就是一个有限制性的属性,通常只用于表单字段或具有表单字段角色的元素,比如listboxtextbox

ARIA中的有34(ARIA 1.1版本中有38)个属性,下图是1.0中有关于属性的周期表:

ARIA的属性主要分为四类:

部件的属性(Widget Atrributes)

部件属性包含特定于GUI系统或富应用程序中常见的用户界面元素的属性,这些元素接收用户输入并处理用户操作。这些属性用于支持部件角色(Widget Roles):aria-autocompletearia-haspopuparia-labelaria-levelaria-multilinearia-multiselectablearia-orientationaria-readonlyaria-requiredaria-sortaria-valuemaxaria-valueminaria-valuenowaria-valuetext

部件属性可以由用户代理(比如浏览器)映射到平台可访问性API状态,也可以由ATs技术访问,或进可以直接从DOM访问它们。用户代理必须通过DOM属性更改事件或平台可访问性API事件提供一种方法,以但在状态更改时通知辅助技术。

Live区域的属性(Live Region Attributes)

Live区域的属性包含特定于富应用程序中Live区域的属性。这些属性可以应用于任何元素。这些属性的目的是指出可能在元素没有焦点的情况下发生内容更改,并为辅助技术提供关于如何处理这些内容更新的信息。有些角色为特定于该角色的aria-live属性指定默认值。Live区域的属性主要有:aria-atomicaria-livearia-relevant

拖拽的属性(Drag-and-Drop Attributes)

HTML5中提供了拖拽相关的API,根据这些API我们可以实现一些拖拽的交互效果。同样的,在ARIA的属性中也有相应的属性。这些属性指示有关拖拽接口元素的信息,比如可拖拽元素及其拖拽目标。拖拽目标信息将由作者可视化呈现,并通过另一种方式提供给ATs。

ARIA规范中有关于拖拽的属性主要有aria-dropeffect

有关于这方面更详细的介绍,还可以阅读:

关系属性(Relationship Attributes)

ARIA中的关系属性表示无法从文档结构确定的元素之间的关系或关联。主要包含:aria-activedescendantaria-colcountaria-colindexaria-colspanaria-controlsaria-describedbyaria-detailsaria-errormessagearia-flowtoaria-labelledbyaria-ownsaria-posinsetaria-rowcountaria-rowindexaria-rowspanaria-setsize

状态(States)

静态的Web页面和动态的Web页面(应用程序)之间最重要的区别可能是表态的页面或应用程序中的元素往往会根据用户交互和定时事件发生变化。应用程序中任何时候都有可能会发生变化,换句话说,其中的元素处于某种状态是个临时状态。对于一个可访问性的应用而言,跟踪交互元素的状态也是非常的重要

在应用程序中,元素的状态通常以可视方式表示。但在一些ATs(比如屏幕阅读器)中,情况并非如此:它(状态)必须是想象出来的。比如游戏中的“矮人”角色已经戴上了隐形的斗篷,你可能会想把它写在角色表上,利于自己能记住。类似地,在元素上写入aria-hidden这样的状态属性也可以确保记录可访问的隐藏状态(告诉屏蔽阅读器,这个元素被隐藏了)。

另外,在ARIA中像aria-expanded这样的状态属性是根据布尔值来反馈给ATs的。比如,JAWS和NVDA Windows屏幕阅读器将一个aria-expanded=“false”会读为折叠,对于aria-expanded=“true”状态会读为展开。这个状态对于使用无语义化的HTML标签创建手风琴组件是非常有益的(事实上,HTML中并没有专门用来制作手风琴的标签元素)。

ARIA的状态和属性有点类似,也分为部件aria-currentaria-checkedaria-disabledaria-expandedaria-hiddenaria-invalidaria-pressedaria-selected等)、Live区域aria-busy)、拖拽aria-grabbed)三种类型的状态。

有关于ARIA的角色、状态和属性更全面的列表,还可以点击这里查阅

HTML标签和WAI-ARIA的关系

2014年,W3C正式发布了HTML5推荐标准。该标准带来了一些变化,比如说提供了一些具有语义化的标签元素(如<header><main><aside><nav><footer>等)以及像hiddenrequired这样的标签属性。随着这些新的HTML5元素和属性的添加以及浏览器对其支持更友好,很多人开如认为ARIA的某些部分(比如Landmark Roles和一些像aria-hidden的属性)就已经过时了,或者说至少没有以前那么重要了。

在浏览器支持具有ARIA等价的隐式角色的HTML标签元素的情况下,通常不需要将ARIA添加到元素中。事实上这种说法并不准确,虽然浏览器对HTML5新增的标签元素得到了较好的支持,而且这些标签元素具有较强的语义化,但很多时候对于部分辅助技术(比如屏幕阅读器)表达并不完全一致。换句话说,有些屏幕阅读器对这些具有语义化的标签元素并不怎么友好。另外,ARIA仍然有很多角色、状态和属性,在任何版本的HTML中都不可用,因此在很长的一段时间内ARIA仍然有很大的作用。

对于使用ARIA,W3C有一条非常重要的规则:

如果你可以使用具有语义化的HTML标签或属性就没有必要给这些标签元素添加ARIA角色、状态或属性来增强可访问性

这条规则对于初次接触ARIA的同学来说非常有效,但在很多时候我还是提倡语义化的HTML标签元素或属性应该和ARIA的角色、属性和状态结合起来使用。这样对于构建可访问性应用更佳。假设大家和我一样,都是初次接触ARIA。因此我们可以使用具有语义化的<button>元素来替代使用ARIA定义的按钮角色:

<!-- 使用ARIA的角色 role="button" 来定义按钮 -->
<div role="button">Here is a snazzy button</div>

<!-- 使用具有语义化的HTML标签元素 -->
<button>Here is a snazzy button</button>

<!-- ARIA和HTML结合大一起使用 -->
<button aria-describedby="some-other-element">Here is a snazzy button</button> 
<div id="some-other-element">This page will self destruct in 10 seconds.</div>

从技术上来讲,上面的三种方案都传达了相同的语义,但最大的区别是ARIA版本要求我们使用附加代码定义按钮角色的功能,而HTML版本则直接使用了浏览和辅助技术支持的原生标签元素<button>。对于ARIA和HTML结合使用,虽然代码有额外的增加,但该方式是一个更佳的方案,除了确保浏览器能识别有语义的标签元素之外,还可以让更多的辅助技术知道按钮的用途的额外信息。对于可访问性来说,这是好事。

也就是说,HTML标签元素(特别是具有语义化的标签元素)和ARIA是相互互补的,因为很多具有语义化的HTML标签元素在ATs上不一定能得到较好的支持,因此在HTML标签元素上添加ARIA的角色、属性和状态对于可访问性是有必要的。当然,这样的描述对于后面要介绍的ARIA使用规则略有差异,但这并无大碍,因为我们的使用一切应该从实际测试得到的结论出发。

何时应该使用WAI-ARIA

当需要语义使Web应用程序可以理解时,应该使用WAI-ARIA。在Web应用的构建中常常为了配合UI视觉的统一,会采用无语义化的标签元素,比如使用div标签元素来实现一个<input type="checkbox" />(很有可能还会借助一些JavaScript脚本)。对于这样的场景,你可以将WAI-ARIA的角色checkbox添加到该div,使其具有一个复选框的语义。

尽管如此,当有一个原生HTML标签元素可用时,比如<input type="checkbox">,我们应该尽可能的使用这些原生的HTML标签元素,就这一点,在《A11Y 101:编写HTML时要考虑可访问性》一文中和大家一起聊到过,HTML语义化标签Web应用可访问性的优势。

原生的HTML标签元素是标准化的,因此更有可能得到跨浏览器和辅助技术的支持

前面也多次提到过,对于原生HTML标签元素,没有必要使用WAI-ARIA(社区中很多同学都是这么建议的,但也有部分专家也建议具有语义化的HTML标签元素也应该和ARIA配合使用)。这里暂且抛开ARIA,只聊有语义的HTML原生标签元素。比如前面的示例,在<button>这样的原生HTML标签(而且是具有语义的HTML原生标签元素)上就不需要再使用role="button"这样的ARIA角色。不过,这条规则也有几个例外。对于一些较新的HTML5语义化标签,比如<nav><header><main><footer>,更建议将相应的ARIA的地标角色(Landmark Roles)添加到这些HTML5元素上,比如<nav role="navigation">。这样做的好处是可以适应浏览器和ATs对这些元素的不一致支持。如果你使用HTML验证器来检测你的HTML代码的话,对于ARIA的角色同样会发出警告信息,只不过这些警告信息是安全的,你完全可以忽略不计。

但并不总是可以在任意具有语义化的HTML标签元素上添加ARIA的角色、状态和属性。比如:

<h3 role="button">Something</h3>

这样做的话会造成语义化的混乱,也可能破坏文档的结构。在这种情况下,更好的方法是将<h3>放在一个无语义化的<div>标签元素内,然后将相应的ARIA角色role="button"赋予给该div,这样既可以保留标题的语义,也可以增强你需要的按钮语义:

<!-- 不好的用例 -->
<h3 role="button">Chapter 2</h3>

<!-- 较好的用例 -->
<div role="button"><h3>Chapter 2</h3></div>

如果要对ARIA应该在什么时候使用更佳的话,我们可以按下面几个原则来做相应的判断:

  • ARIA 的 角色 属性值可以作为地标来复制HTML5 元素的语义化(例如 nav)。或者超越HTML5 的语义,给不同的功能块提地标角色,例如 search, tabgroup, tab, listbox 等等
  • 屏幕阅读器往往难以报告一直变化的内容,用无障碍特性我们能使用 aria-live 来通知屏幕阅读器某一部分的内容更新了。例如XMLHttpRequest 或者 DOM APIs
  • 默认的HTML 元素是具有自带的键盘辅助功能的。当其他元素与JavaScript一起进行交互时,键盘的辅助功能和屏幕阅读器的报告会因此收到影响(例如你将会难以用tab 到达理想的位置)。这是无法避免的,WAI-ARIA 提供了提供了一种允许其他元素获得焦点的方法 (使用 tabindex
  • 当一系列嵌套的 <div> 与CSS、JavaScript一起用于创建复杂的UI功能,或者通过JavaScript大大地增强或者更改原生的控件,可访问性将会变得极其困难——屏幕阅读器将会难以找到语义内容线索。在这种情况下,AIRA 可以帮助提供缺少了的功能,例如 button, listbox,或者 tabgroup,另外和aria-requiredaria-posinset这样的属性可以提供有关功能的更多线索

你只在需要的情况下使用可访问性特性!理想的情况下,你只要用原生的HTML 来实现屏幕阅读器所需的语义化内容即可。有些时候这是不可能的,一来是你对于代码的整体的控制是有限的,另一方面是总会有复杂到原生HTML 无法支持的功能需要你实现。在这个场景下,WAI-ARIA 将会变成有价值的可访问优化功能。

但还是要重申:当你需要的时候再使用可访问性特性(即ARIA的角色、状态和属性)!

简单小结一下

通过前面的学习,我们对WAI-ARIA有一定的了解:

ARIA定义HTML属性来传递正确的角色、状态、属性和值

WAI-ARIA可以为可访问性提供更详细的信息:

  • 角色:这是什么类型的“东西”?比如,它是一个button
  • 状态:它的情况如何?比如disabledchecked
  • 名称:它的名称或标签是什么?
  • 关系:它是否以某种方式与页面中的其他元素相关联?

浏览器会将这些信息映射到操作系统的可访问性 API,并将其公开给辅助技术。一旦ATs理解了含义(或目的),它可以自动读出特定的提示(或相关信息)。

使用WAI-ARIA主要可以做到:

  • 让辅助技术用户(比如屏幕阅读器)可以理解定制的小部件(Widget)
  • 以编程方式指示元素之间的关系
  • 通知用户动态更新
  • 向辅助技术隐藏不相关的可视内容
  • 未完全重新编码的不可访问内容的修复

但WAI-ARIA并不是万能的,他的使用也是会受到一定限制的,特别是ARIA的角色和属性的使用:

  • 某些角色只能作为特定复杂小部件的一部分(详细请见ARIA的角色一节)
  • 一些aria-*属性是全局的
  • 其他aria-*属性只适用于特定的角色
  • 并不是所有的角色或属性都得到一致的支持或实现

虽然WAI-ARIA在某些场景之下能增强HTML标签的语义化,但这并不代表他是万能的。特别是对于刚接触WAI-ARIA而言,你始终要记得WAI-ARIA不是魔法,也不是万能的,他仅仅只是改变了辅助技术解释内容的方式。具体的说,WAI-ARIA还有很多事情是做不到的:

  • 不能让非聚焦的元素得到焦点
  • 提供适当的键盘绑定
  • 改变浏览器的行为
  • 自动维护或更新属性
  • 变化可见的UI视觉(除CSS使用特性的选择器重构UI风格之外)

可以说,WAI-ARIA是一个庞大的体系,涉及的知识点和相关信息也非常的多,要用好WAI-ARIA并不是一件易事,但我们遵循一些使用规则会让其变得更简单些。

使用WAI-ARIA的规则

对于WAI-ARIA的使用,特别是针对于初学者而言,一直有一句这样的名言:

NO ARIA is better than bad ARIA!

虽然WAI-ARIA代表“可访问的Web富应用程序”,有助于有障碍人群更容易访问你的Web页面或Web应用程序。它特别有助于使用Ajax、HTML、JavaScript和相关技术开发动态内容和高级用户界面控件

WAI-ARIA规范中有关于角色、状态和属性等特性和CSS或者HTML5中的一些标签元素类似,虽然在很多年前就创建了,但各种平台(比如浏览器,ATs等)对其支持度也有一定的差异性。而且开发人员对这方面了解的也不够全面,特别是初次接触或刚使用一段时间的开发人员,总是避免不了滥用WAI-ARIA的情况。与其开发人员不使用WAI-ARIA相比,误用WAI-ARIA会给可访问性带来更差的体验。这就是为什么会说:“没有ARIA比乱用ARIA更好”。

也就是说,开发人员应该努力理解并遵守WAI-ARIA的规则,这样才能创建更具可访问性的Web应用,才能给有障碍的人群提供一个更佳的访问体验。接下来,我们就来看看使用WAI-ARIA的一些常见规则。

WAI-ARIA的使用规范中提供了五个基本规则。

规则1:不要使用WAI-ARIA,使用原生HTML标签元素

If you can use a native HTML element HTML5 or attribute with the semantics and behaviour you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.

在使用WAI-ARIA之前,首先使用原生的HTML标签元素或属性。如果使用的HTML或属性不具语义化,那么使用WAI-ARIA。

比如我们在页面上有一个链接,那么我们就不应该使用非<a>标签元素:

<!-- 不应该这么使用 -->
<span tabindex="0" role="link" onclick="document.location='//www.w3cplus.com'" onkeyup="...">Let's G0!</span>

<!-- 更应该使用原生的HTML标签元素 -->
<a href="//www.w3cplus.com">Let's Go!</a>

其实这样的案例特别的多,特别是当今天总是依赖于JavaScript的优秀框架(比如React、Vue等),很多同学都喜欢使用非语义化的div标签。另外还有一些场景,比如表单控件的场景中,因为表单控件在不同的浏览器渲染出来的UI效果都不一样,所以会使用div和JavaScript来定制化来不及单控件,比如大家喜欢的checkbox(复选框)。虽然使用<div role="checkbox">能将语义传递给使用辅助技术(比如屏幕阅读器)的人,但使用ARIA和HTML不同的是,使用原生HTML无需任何额外的工作,因为它已经映射到可访问性API上。

现在的问题是什么时候,什么样的场景之下我们需要使用ARIA,例如:

  • Web网页或Web应用程序不是从零开始设计,而是在中途进行可访问性的改造。在这种情况之下,为了节省时间、精力和金钱,最好的方案就是使用ARIA
  • 如果无法对原生HTML元素设置样式,那么可以使用ARIA构造自定义元素和样式并为元素提供语义
  • 如果所需的语义在宿主语言(HTML 5.x)中没有出现,那么使用ARIA来传达语义。例如,传达树形的语义就可以使用ARIA,因为HTML5并没有这样的元素或属性
  • HTML5中虽然提供了部分具有语义化的标签元素,但在不同的平台上支持度还是有较大的差异,在这种情形之下,可以继续使用ARIA,最好的方式是HTML和ARIA一起使用,比如<main role="main">
  • 如果视觉设计约束排除了使用特定的原生HTML标签元素的可能性,因为该元素不能按要求设置样式,这个时候最好使用ARIA,比如视觉设计强制所有平台的复选框或单选按钮要有一致的样式风格
  • 如果该特性目前在HTML中还无法使用,可以使用ARIA

规则2:除非真的需要,否则不要更改原生语义

如前所述,大多数HTML元素或属性都传达能较好的传达语义(事实上,HTML中除了divspan是无语义化标签之外,其他的标签都具有相应的语义)。那么在使用的时候,除非真的有需要改变原生标签元素的语义,否则我们不应该使用ARIA来更改原生标签的语义。比如下面这个示例:

<!-- 不要像下面这要使用ARIA来改变原生标签元素h1的原生语义 -->
<h1 role="button">Heading button</h1>

这样做改变了标题<h1>原有的语义(标题变成了按钮),这样对于ATs用户(比如屏幕阅读器用户)就无法快速导航到它。我们应该根据第一条规则来使用,比如使用原生的HTML标签元素:

<!-- 使用原生HTML标签元素 -->
<h1><button>Heading button</button></h1>

即使你实在喜欢ARIA,那也应该像下面这样使用会更合理一些:

<!-- HTML和ARIA结合使用,增强语义 -->
<h1><span role="button">Heading button</span></h1>

注意:如果将非交互元素用作交互元素,那么开发人员必须使用WAI-ARIA来添加语义,并使用JavaScript脚本添加适当的交互行为。比如上面示例中的button,使用原生的HTML标签元素<button>会更好,更能节省不少工作量。

规则3:所有交互式ARIA控件必须与键盘一起使用

如果你创建一个小部件(UI控件),用户可以点击触摸拖拽滑动滚动,那么同时这些小部件也都必须可聚焦,可以使用键盘进行导航以及做一些相同的操作。

例如,如果使用role="button",则元素必须能够接收焦点、用户必须能够使用Enter(或Return)和空格键激活与元素相关的操作。有关于这方面更详细的介绍,可以参阅WAI-ARIA实战键盘和结构导航设计模式和小部件等章节中的内容。

规则4:不要让可聚焦元素变得中立(Neutralise)

这里所说的“不要让可聚焦元素变得中立”指的是“不要在可聚焦元素上使用role="presentation"aria-hidden="true"”。

在《使用tabindex的正确姿势》一文中,我们了解到焦点对于可访问性的作用。如果你还没有阅读该文的话,我建议你花点时间阅读一下这篇文章。

在了解为什么不要在可聚焦元素上使用role="presentation"aria-hidden="true"之间,我们先简单的了解一下这两个东东:

  • aira-hidden="true":它是ARIA的一个状态属性。表示元素和它的所有子元素都是不可见或不可感知的
  • role="presentation":它是ARIA中的一个角色。它会隐藏原生标签元素的语义,同时也不会将原生语义映射到可访问性API上

在Web中,使用CSS可以对元素做一些隐藏(比如《Web隐藏术》中聊到的一些隐藏述),这里所聊到的隐藏术,有些对视觉呈现有影响,有些除了影响视觉呈现之外对于ATs也会有一定的影响。具体的如下表所示:

隐藏术 是否有盒模型 是否影响布局 *屏幕上是否可见 屏幕阅读器是否可读 元素是否可操作
display: none
hidden="true"
visibility:hidden
opacity:0
position:absolute
clip-path
mask
transform
改变盒型尺寸

对于aria-hidden="true"role="presentation"有着相似之处,不同的是:

  • aria-hidden="true"可以向ATs隐藏可呈现的内容,前提是隐藏这些内容的行为是为了通过删除冗余或无关的内容来改进辅助技术用户的体验。使用aria-hidden向屏幕阅读器隐藏可见内容的开发者必须确保向辅助技术公开相同或相同含义和功能
  • role="presentation"可以用于隐藏语义化。比如说,一个元素被用来改变页面的外观(比如分隔图、装饰性图像等),它不具备元素类型所暗示的所有功能性、交互性或结构相关性,或者在不支持WAI-ARIA的浏览器中提供可访问的回退时,可以用该属性

既然你已经使用了可聚焦元素,那么你在当初的设计之时就有可能有可访问的需求,比如给行动有障碍的用户群体提供用于键盘操作来访问应用。这个时候我们就不应该在可聚焦的元素上使用role="prsentation"aria-hidden="true"。因为role="prsentation"会否定访问树中的语义,aria-hiddentrue时会向可访问性API隐藏内容或元素,对于ATs用户来说都不会有任何方式交互。这样就有背于你设计的初衷。

简单地说,在可见的可聚焦元素上定义这些属性会导致一些用户都无法获取到对应的内容或元素(比如屏幕阅读器)。

先来看一个不好的示例:

<!-- 失去了button原有的语义 -->
<button role="presentation">press me</button>
<button aria-hidden="true">press me</button>

<!-- 失去了设置tabindex的意义 -->
<span tabindex="0" aria-hidden="true">...</span>

另外,将aria-hidden应用到可见交互元素的父元素(或祖先元素)也会导致交互元素被隐藏,所以下面这样的做法也是不好的,请尽量不要这么做:

<div aria-hidden="true"> 
    <button>press me</button>
</div>

话又说回来,使用CSS隐藏内容是件容易的事情,但要把可访问性方面考虑进来,这事情就变得没有那么简单的了。比如前面的表格中所列的,有些样式只是视觉上隐藏,有些样式除了视觉上的影响对ATs方面也会有影响。那么加上aria-hiddenrole="presentation"role="none"等,事情就会变得更为复杂。要用好他们也就不是容易的事情,必须要对他们之间的知识要有所了解。这里推荐你阅读:

后续我们也会针对这方面再做进一步的探讨,感兴趣的话欢迎持续关注后续相关的更新。

规则5:所有交互元素必须有一个可访问的名称

Web页面上或Web应用上的所有交互元素(比如链接、按钮、文本字段、组合框、单选按钮和复选框等)必须具有可访问的名称。没有可访问的名称,ATs无法理解控件的全部内容。要提供可访问的名称,有一些可用的技术,它们因控件而异。我们简单来看看其中的一些:

  • HTML链接和按钮:无论我们给链接提供的文本(或者给按钮提供的值)是什么,它都会成为可访问的名称
  • 输入文本字段:为了提供可访问的名称,表单控件需要隐式或显式地与其他标签相关联
  • 定制的小部件:为了为定制小部件提供可访问的名称,开发者可以使用aria-labelaria-labelledby

先来看一个不好的示例:

<!-- 不要像下面这样做,没有提供可访问的名称 -->
<span tabindex="0" role="button">
    <span class="glyphicon glyphicon-remove"></span>
</span>

上面示例通过role="button"span元素定义为一个按钮角色,算是一个可交互元素,但又没有隐式或显式为其添加可访问名称,这样的做法是不对的。试问一下,大家平时开发的时候,是不是也有犯过类似的错误。

WAI-ARIA 1.1规范的第5.2.7一节中,专门介绍了可访问名称相关的知识。我们可以像下面这样为可交互的元素添加可访问名称:

<span tabindex="0" role="button">
    <span class="glyphicon glyphicon-remove">
        <span class="...">Delete</span>
    </span>
</span>

<span tabindex="0" role="button" title="Delete">
    <span class="glyphicon glyphicon-remove"></span>
</span>

我们也可以借助ARIA中的aria-labelaria-labelledby给可交互元素添加可访问名称:

<span tabindex="0" role="button" aria-label="Delete">
    <span class="glyphicon glyphicon-remove"></span>
</span>

<span tabindex="0" role="button" aria-labelledby="delete-des">
    <span class="glyphicon glyphicon-remove"></span>
</span>
<span id="delete-des" class="sr-only">Delete</span>

但是使用aria-labelaria-labelledby(甚至是aria-describedby)等属性时要特别小心,因为它们不能与所有HTML元素一起工作。

详细的描述可以阅读Using ARIA中的第10小节或者阅读:

虽然说这五条规则不一定能完全让你在使用ARIA不出错,但至少遵循这些规则可以让你减少不少困惑和烦恼。建议大家在使用ARIA时能尽可能的遵循,如果既不了解ARIA,又不遵循这些规则,那么只能是“NO ARIA is better than bad ARIA!”

不正确使用WAI-ARIA是危险的

前面多次提到过并也也倡议:不使用ARIA比不正确的使用ARIA要更好。除了遵循上面提到的五条ARIA使用规则之外,我们还应该尽量避免一些错误的使用姿势。因为这些错误的使用姿势对于Web页面或Web应用可访问性来说是极其危险的。我们在使用ARIA时应该尽可能的避免这些错误。

不正确的ARIA语法

大多数技术都有着自己独特的语法规则,ARIA也是一样。没有使用正确的ARIA语法可能会导致意外的结果。开发者在开发中使用ARIA时常常会犯下面这些语法错误:

  • ARIA角色值大小写不区分:ARIA中的角色值定义页面中对象的用途。使用ARIA的角色时,应该严格遵循WAI-ARIA规范中角色中罗例的值来使用,而且要区分大小写。比如,你要是大代码中使用role="Button"(或role="BUTTON")的话,辅助技术无法正确的识别出对象。因此大家要记住,ARAI中角色值是有大小写敏感的,需要用小写字母输入,最好严格按照规范中列表来使用
  • 属性或值的拼写错误:人肉编码总是难免会拼错字母,那么ARIA的属性或值也会常常被开发者拼错,而这样的错误直接会让屏幕阅读器无法知道控件的正确内容或信息。比如,把aria-label拼写成了aira-label或者aria-lable等都是错误的(在WAI-ARIA中找不到的属性或值都会被认为是错误的)
  • 不正确或无效的角色声明:开发者经常会使用不正确地角色声明。比如,将role="button"用成了aria-role="button"。结果屏幕阅读器无法正确的识别出。
  • 角色不存在或无效:开发者使用了WAI-ARIA中不存在的角色。开发者会凭着自己的理解为元素添加一些不存在角色,比如为错误消息提供一个role="error",试图传递错误语义,但WAI-ARIA中却没有这样的东东。这样一来,屏幕阅读器这样的辅助技术也无法正确识别。

上面这些点,其实都容易克服的,只要开发者在平时开发的时候有足够的仔细就可以较好的避免。另外,你可能会说自己记不住WAI-ARIA规范中所有的角色、状态、属性的值,但也没有太大的关系,在使用的时候你可以把该规范时刻准备着。

父子角色没配合好

开发者经常在对象上定义子角色,而不首先定义父角色。你可能知道,没有相关的父(上下文)角色,就不能使用某些角色。例如,可以在不定义tablist父角色的情况下定义tab角色。

因此,屏幕阅读器可能无法正常工作。一些屏幕阅读器和浏览器组合仍然会提供预期的信息,但是其他的则不会,这使得它成为一个非常不可靠的解决方案。所以开发在使用ARIA的角色时,特别是有父子(上下文)关系的角色,在使用的时候需要确保为子角色提供父角色(或上下文相关的角色)

相反,开发者为对象提供了父角色,但没有提供子角色。如果不定义它们相关的子角色,就有可能会缺失一些角色。这样对于一些屏幕阅读器有可能无法传达预期的关系。

无效ID值的引用

在HTML中<label>for和一些<input>id是一一对应关系的,如果在for中引入的id是错误的或者是无效的,那么他们之间的关系就相应的不存在了。在WAI-ARIA的使用中也是类似的,如果你通过aria-labelledbyaria-describedbyaria-errormessage属性和别的元素关联在一起,要是这些属性接收的id值不存在或无效,那么屏幕阅读器将无法创建引用户并按预期识别相应的信息。为了避免这些情况,开发者需要确保WAI-ARIA属性定义的id值是有效的,而且引用的时候是正确的(千万不能出现手误)

WAI-ARIA属性使用不正确

很多开发者在使用WAI-ARIA属性的方式与规范中定义的方式相反。为了可访问性做得更好,某些属性只能用于特定的角色上。例如,开发者可能会把aria-selected="true"用到锚点或链接元素,会认为屏幕阅读器会识别锚点或链接的选中状态。但是,根据WAI-ARIA规范,在链接元素上是不允许使用aria-selected的,因此屏幕阅读器并不会识别出该元素的选择状态,也不会读出来。另一方面,当aria-selectedtreeitemtab这样的角色一起使用时,对于屏幕阅读器的用户来说可访问的体验是要更好的。所以大家在使用的时候,需要将属性于正确的角色上

上面是开发者把不必要的属性添加到了角色上。除此之外,开发者定义WAI-ARIA角色时没有定义预期的必需属性。如果不定义预期的必需属性,就不能定义某些角色。例如,当在对象上定义role="slider"(滑块角色)时,就必须在该角色上定义特定的属性,比如aria-valueminaria-valuemaxaria-valuenow。如果没有这些特定的属性,将会导致屏幕阅读器不能按照预期传递信息。因此,大家使用特定的角色时,需要确保为该角色提供了所必须的属性

从上面几点来看,主要是手误和没有按照WAI-ARIA来使用。手误需要避免或者借助检测工具来监测,没有按照WAI-ARIA规范使用,主要原因还是不熟悉,没掌握好,这个需要大家多花点时间去学习。

WAI-ARIA的最佳实践和案例

前面我们花了很大的篇幅和大家一起探讨了WAI-ARIA相关的知识,但这些都是一些理论和基础性的东西。就我个人而言,要掌握一项技术,最好的方式就是在实际项目中实践。对于WAI-ARIA的实践在社区中有很多,比如:

如果你有足够的耐心,阅读完上面的规范并且照着规范去实践,你肯定会成为这方面的高手。就算你没有足够的时间去折腾,你只要在构建自己的组件或写Web页面的时候能按照WAI-ARIA中的设计模式和控件一节去做,也能构建一个具有高可访问性的组件:

接下来和大家一起来构建一个简单的小示例,让一个列表能按照字母的顺序进行排序。通过这样的小示例将学到的WAI-ARIA中有关于角色、属性和状态的知识付诸实践。

目标

我们想要实现一个类似下图的效果:

了解HTML的同学应该都知道,在HTML中并没有一个叫<toolbar>的元素,除非我们通过相关的技术手段创建一个Web组件。不管是使用什么方式来创建,<toolbar>都不是标准的HTML元素。因此,我们构建这个组件的时候,希望其具有可访问性,那么我们就需要运用到前面学到的知识。在WAI-ARIA中有一个角色,他就叫toolbar

从WAI-ARIA规范中我们可以获知,该角色没有必需的属性和脚本,但具有几个可选属性,比如aria-labelledbyaria-labelaria-orientationaria-activedescendant。实现<toolbar>可能有几个注意项:

  • toolbar是常用功能是按钮和一个控件的集合,并且以紧凑的可视形式呈现
  • 如果aria-orientation未显式设置值为vertical的话,则会采用隐式的horizontal
  • toolbar角色通常不需要依赖JavaScript脚本,因为焦点将遵循工具栏容器中包含的活动元素的原生选项卡(Tab)顺序
  • 如果工具栏容器接收到焦点,则可聚焦元素必须与包含role="toolbar"属性的元素相同,且必须使用aria-activedescendant引用所激活的元素的id来管理焦点,而不需要移动焦点

换句话说,我们可以根据上面的几点来构建一个具有可访问性的toolbar

步骤1:创建toolbar容器,并赋予toolbar角色

首先创建一个容器,该容器可以是任意的HTML标签,建议使用无语义的div标签,并在该标签上显式的声明role="toolbar"角色:

<div role="toolbar">
    <!-- 工具栏功能都在这里 -->
</div>

在我们的设计中,toolbar的目的是清晰的,因为它的视觉关系到它所影响的内容。但是,这并不会在屏幕阅读器上传递任何信息,因此我们需要在toolbar上使用aria-label属性提供一个可访问的名称:

<div role="toolbar" aira-label="排序选项">
    <!-- 工具栏功能都在这里 -->
</div>

接下来在toolbar里面添加两个button作为我们的控件:

<div role="toolbar" aira-label="sorting options">
    <button>A to Z</button>
    <button>Z to A</button>
</div>

即使没有给toolbar添加其他的ARIA属性和状态,我们已经改善了用户(比如屏幕阅读器用户)能较好的识别toolbar。当用户将焦点移到第一个按钮时,会通过toolbararia-label告诉用户它是什么。

步骤2:构建组件中的上下文关系

接下来,我们可以在toolbar上通过aria-controls和另一个元素的id创建关联的关系,这样就可以很好的传递他们之间的关系(也就有了上下文之间的联系):

<div role="toolbar" aria-label="sorting options" aria-controls="sortable">
    <button>A to Z</button>
    <button>Z to A</button>
</div>

<ul id="sortable">
    <li>Fiddler crab</li>
    <li>Hermit crab</li>
    <li>Red crab</li>
    <li>Robber crab</li>
    <li>Sponge crab</li>
    <li>Yeti crab</li>
</ul>

注意,我们在toolbar本身添加了aria-controls,而不是在每个单独的按钮上添加。其实在按钮上添加也是可以的,只不过这种方法更简洁,只需要使用一次,而且按钮应该被视为控制工具栏的独立组件。

屏幕阅读器和浏览器有点类似,并不是所有屏幕阅读器都支持这方面的特性。也就是说一些屏幕阅读器很少能用这样明确地处理这种信息关系。如果拿JAWS来说,它有一些键盘命令,使用户可以将焦点移动到受控制元素:使用JAWS + Alt + M移动到受控元素。

步骤3:按钮的按下和释放

就该示例而言,按钮有选中(selected)或按下(pressed)状态。这个时候aria-pressed状态属性就有发挥作用的地方了,当aria-pressed的值为true时表示按钮处于按下状态,反之为false时,按钮为释放(未按下)状态。按钮的状态并无法直接使用HTML、CSS或者说ARIA来控件,需要借助JavaScript脚本来进行切换。在页面加载完成时,只有第一个按钮的aria-pressed值设置为true

<div role="toolbar" aria-label="sorting options" aria-controls="sortable">
    <button aria-pressed="true">A to Z</button>
    <button aria-pressed="false">Z to A</button>
</div>
<ul id="sortable">
    <li>Fiddler crab</li>
    <li>Hermit crab</li>
    <li>Red crab</li>
    <li>Robber crab</li>
    <li>Sponge crab</li>
    <li>Yeti crab</li>
</ul>

我们可以通过CSS的:active或者属性选择器对按钮按下的状态做一个区别化处理:

button:active, button[aria-pressed="true"] {
    position: relative;
    top: 3px; 
    box-shadow: 0 1px 0 #222; 
}

键盘焦点控制

我们很多用户是高度依赖于键盘操作的,所以为了让toolbar可访问性好一点(除了便于屏幕阅读器用户操作之外,还应该考虑其他用户群体,比如高度依赖于键盘操作的用户)。这个时候我们可以通过tabindex来控件,比如说在列表中添加tabindex="-1"

<div role="toolbar" aria-label="sorting options" aria-controls="sortable">
    <button aria-pressed="true">A to Z</button>
    <button aria-pressed="false">Z to A</button>
</div>
<ul id="sortable" tabindex="-1">
    <li>Fiddler crab</li>
    <li>Hermit crab</li>
    <li>Red crab</li>
    <li>Robber crab</li>
    <li>Sponge crab</li>
    <li>Yeti crab</li>
</ul>

最终示例代码:

到这里,有关于toolbar的组件我们算是完成了。最终的代码可能像下面这样:

<!-- HTML -->
<div role="toolbar" aria-label="sorting options" aria-controls="sortable">
    <button aria-pressed="true">A to Z</button>
    <button aria-pressed="false">Z to A</button>
</div>
<ul id="sortable" tabindex="-1">
    <li>Fiddler crab</li>
    <li>Hermit crab</li>
    <li>Red crab</li>
    <li>Robber crab</li>
    <li>Sponge crab</li>
    <li>Yeti crab</li>
</ul>

// CSS略去

<!-- jQuery -->
$('[role="toolbar"] [data-sort]').on('click', function() {

    if ($(this).attr('aria-pressed', 'false')) {

        var listToSort = $('#' + $(this).parent().attr('aria-controls'));
        var listItems = listToSort.children();
        var array = [];

        for (var i = 0, l = $(listItems).length; i < l; i++) {
            array.push($(listItems[i]).text());
        }

        if ($(this).attr('data-sort') === 'ascending') {
            array.sort();
            console.log(array);
        }

        if ($(this).attr('data-sort') === 'descending') {
            array.sort();
            array.reverse();
            console.log(array);
        }

        for(var i = 0, l = $(listItems).length; i < l; i++) {
            listItems[i].innerHTML = array[i];
        }

        $(this).siblings().attr('aria-pressed', 'false');
        $(this).attr('aria-pressed', 'true');

    }

});

$('[role="toolbar"] [data-sort]').on('keydown', function(e) {

    var $newButton;
    var $nextButton = $(this).next();
    var $prevButton = $(this).prev();
    var $listToSort = $('#' + $(this).parent().attr('aria-controls'));

    switch (e.keyCode) {
        case 37:
        $newButton = $prevButton;
        break;
        case 39:
        $newButton = $nextButton;
        break;
        default:
        $newButton = false;
        break;
    }

    if ($newButton.length) {
        $newButton.focus();
    }

    if (e.keyCode === 9) {
        if (!e.shiftKey) {
        e.preventDefault();
        $listToSort.focus();
        }
    }
});

这里演示的仅仅是有关于WAI-ARIA中的一个小示例。

小结

这篇文章仅仅是WAI-ARIA中的初级内容,但可以帮助你很好的了解WAI-ARIA相关的知识。如果要彻底的了解WAI-ARIA,我们需要花更多的时间和精力去学习和实战。

扩展阅读

返回顶部