Element 中的 AT 可访问性

1,228 阅读6分钟
原文链接: zhuanlan.zhihu.com

定义:AT是什么?

AT 是 Assistive Technologies 的简写,是指具有以下特征的硬件或者软件:

  • 依赖 UA(User Agent,下文简称”UA“) 提供的服务来提取和呈现页面内容
  • 通过 API 和 UA 进行交互
  • 提供了一系列服务来方便残障用户和网站内容进行交互

其中包括:屏幕放大器、屏幕阅读器、文语转换软件、语音识别软件、输入替代设备、鼠标替代设备等;

主要技术:ARIA

可访问的页面内容是指页面结构、页面组件和行为具有语义化,使得 AT 设备能正确理解和传达页面内容给残障用户,ARIA 是 Accessible Rich Internet Applications 的简称,是 W3C 推出的,用来协助和增强 Internet Applications 的可访问性,提高与 AT 的互操作性的规范。ARIA 提供了一组特殊的易用性属性,其中包括 roles, states 和 properties,可以添加到任意标签上,尤其适用于 HTML5。

一、roles:

描述组件的类型和页面组织架构。正确定义 role 信息可以让 AT 知道该如何操作该标签,一般在用户的交互过程中,role 值是不会改变的。如果要改变 role 值,需删除当前元素及其子元素,用带有 role 值的其他元素来替代它。使用方法如下:

<div role=“button”>按钮</div> 语义上等同于 <button>按钮</button>

开发过程中常用的 role 值主要分为以下几类:

Landmark Roles:

包括 main、banner、main、search、navigation 等,用来划分页面结构,AT 设备通过键可在各页面模块之间进行快捷访问和操作。

Widget Roles:

包括 tree、menu、dialog、slider,switch 等,也是 Element 组件库开发中常用到的值,用来描述交互性的 UI 组件的类型。每个 role 值有对应的 states & properties,role 类型之间的存在属性继承和嵌套关系,类似于 HTML5 中 ul,li 之间的关系,role="treeitem" 的元素需包含在 role="tree" 的元素中,这个包含可以是通过关系型的 states & properties 进行关联,也可以是 DOM 结构上的嵌套。

Document Structure:

包括 img、table、list、heading 等,用来描述无用户交互的静态内容;

Live Region Roles:

包括 alert、log、marquee、timer 等,用来描述页面上动态内容区域;

Window Roles:

包括 alertdialog、dialog;用来描述页面上的窗口组件;

二、states & properties:

描述组件的当前状态。states 与 properties 略有差异,一般 states 的值在用户交互过程中会发生变化,如 aria-checked;properties 的值一般是不变的,如 aria-label,但也有一些properties 如 aria-valuenow 会在用户交互过程中发生改变。 states & properties 一般和 role 属性配合使用,AT 可以通过访问 UA 暴露出来的 DOM 信息或者 accessibility API 的映射信息来读取组件或区域当前的状态。当 states & properties 的值在用户交互过程中发生改变时, accessibility API 通过 Event 通知 AT 设备。使用方法如下:

<div role=“button” aria-label="点击有弹窗" aria-haspopup="dialog">按钮</div>

开发过程中常用的 states & properties 主要分为以下几类:

Widget Attributes:

包括 aria-haspopup、aria-disabled、aria-checked、aria-selected、aria-valuemin 等;一般和 Widget Roles 配合使用。

Live Region Attributes:

包括 aria-busy、aria-live、aria-relevant、aria-atomic;一般用在动态内容区域上,如

<div aria-busy="true">loading...</div>

表示内容正在加载或更新中,AT 需等待内容更新完毕才展示给用户;

Relationship Attributes:

包括 aria-activedescendant、aria-controls、aria-labelledby、aria-posinset、aria-errormessage 等;用来连接在 DOM 结构上不能直接看出来关系的元素。

以 Element 中 Tabs 组件为例:

<div role="tablist">
  <div id="tab-first" aria-controls="pane-first" role="tab" tabindex="-1">用户管理</div>
  <div id="tab-second" aria-controls="pane-second" role="tab" aria-selected="true" tabindex="0">配置管理</div>
  <div id="tab-third" aria-controls="pane-third" role="tab" tabindex="-1">角色管理</div>
  <div id="tab-fourth" aria-controls="pane-fourth" role="tab" tabindex="-1">定时任务补偿</div>
</div>

<div class="el-tabs__content">
  <div role="tabpanel" id="pane-first" aria-labelledby="tab-first" aria-hidden="true">用户管理</div>
  <div role="tabpanel" id="pane-second" aria-labelledby="tab-second">配置管理</div>
  <div role="tabpanel" id="pane-third" aria-labelledby="tab-third" aria-hidden="true">角色管理</div>
  <div role="tabpanel" id="pane-fourth" aria-labelledby="tab-fourth" aria-hidden="true">定时任务补偿</div>
</div>

aria-selected=true 表示当前 tab 已被选择,tab 与 tabpanel 元素在 DOM 树上不存在包含关系,通过 aria-controls 进行连接;

ARIA & Host Languages

ARIA 的存在是为了增强 Host Languages(HTML5 or SVG)语义信息,并非替代;

当 Host Languages 的原生标签&属性能满足语义化需求时,推荐使用原生的。当没有原生标签&属性能描述 UI 组件的语义时如开发者使用 div+css+js 构造的 Tree 组件,使用 ARIA进行语义补充;

当标签的原生语义和 ARIA 同时存在一个元素上时,ARIA 具有更高的优先级;也就是说

<a role="button" />  // 会被解读为按钮而不是一个链接;

开发者实践总是快于 ARIA 和 Host Languages 标准的发展,随着新的 UI 组件的推广和使用, ARIA 和 Host Languages 也会不断的有新的语义化标签和属性出来支持开发者。

UA & AT & Accessibility APIs

每个系统都有一套底层的 Accessibility APIs 用于向 AT 设备传递系统自带 GUI 的可访问性信息和交互事件;各 UA 和 AT 设备在不同程度上实现对 Accessibility APIs 的支持,两者通过系统底层的 Accessibility API 进行信息交互; UA 将 DOM 中的可访问信息映射到底层 Accessibility APIs 中,AT设备通过访问底层的 Accessibility APIs 来获取信息,但也有极少 AT 设备是直接通过访问 DOM 结构来获取页面可访问信息。当用户通过 AT 设备进行操作时可以修改 Accessibility APIs 中 states or properties 的值,UA 监听到变化改变 web 应用的状态;图解如下:

实现步骤:

我们以 Element 中的 InputNumber 组件为例,来详细讲解下实现步骤:

1、role & states & properties

  • 首先为组件根元素添加 role="spinbutton" 属性;
  • 添加必填属性 aria-valuenow,如果有最大/小值,添加 aria-valuemin、aria-valuemax属性,aria-valuenow 默认为 0,aria-valuemin、aria-valuemax 默认为无限制;
  • 添加选填属性 aria-labelledby、aria-describedby、aria-disabled 等属性;
mounted() {
  const innerInput = this.$refs.input.$refs.input;
  innerInput.setAttribute('role', 'spinbutton');
  innerInput.setAttribute('aria-valuenow', this.currentValue);
  innerInput.setAttribute('aria-disabled', this.disabled);
  if (this.max !== Infinity) {
    innerInput.setAttribute('aria-valuemax', this.max);
  }
  if (this.min !== -Infinity) {
    innerInput.setAttribute('aria-valuemin', this.min);
  }
}

2、 更新 states & props

当用户交互过程中,组件状态发生变化时,通过js脚本更新相关属性值 aria-valuenow

updated() {
  const innerInput = this.$refs.input.$refs.input;
  innerInput.setAttribute('aria-valuenow', this.currentValue);
}

3、测试

使用 AT 设备如 Voiceover 或 Chrome 插件 ChromeVox 来测试可访问效果;

注意:因每个 UA 和 AT 对 ARIA 和 Accessibility APIs 的支持情况不一样,因此不同的 UA & AT 组合使用的效果会有略微差异;