[铁人赛 Day03] 如何提升你的 React 网站易用性?(Web Accessibility)(中)- Accessible name、Keyboard Accessibility

文章大纲与涵盖范围

继上篇介绍完无障碍网站(Web Accessibility,又称为 a11y)的目的与实践方向,中、下篇将着重在 React Advanced Guide 里提供的 Accessibility 指南。

在可以广泛运用的 Accessibility 实作知识上,hightlight React 对此的支援(例如 JSX 是否也能直接应用某些 HTML 属性?)或者调整(某些属性在 React 里,可能有写法的差异)。

实作 Accessibility 的重点提示

  1. accessible name(无障碍名称)

accessible name 被写在 HTML 元素上,用途在为辅助科技提供更多识别元素的资讯,举个应用场境的例子:使用语音识别工具的人,可以用 accessible name 来锁定想要选去的元素;使用萤幕阅读工具的人,accessible name 让他更理解该元素的意涵。

使用语音识别工具的人,可能会说 “Click Doggy Day Care link“ 来触发 <a> 元素。

<a href="doggy.html">Doggy Day Care</a>

从上面的案例中可以看到,并非每个元素都需要特别添加更多资讯,以下举出三项 accessible name 的实作方式 :

  • 使用属性
  • 使用关联元素
  • 使用 ARIA

A. 使用属性

图片的 accessible name 从 alt 属性衍生而来。

<img src="doggy.png" alt="cute doggy">

如果是 <a> 元素包着 <img> 元素,accessible name 可能会有不同的组合方式。

// accessible name 是 img 的 alt "cute doggy"
<a href="doggy.html"><img src="doggy.png" alt="cute doggy"></a>
// accessible name 是 img 的 alt 加上文字 "cute doggy 待认养"
<a href="doggy.html"><img src="doggy.png" alt="cute doggy"> 待认养</a>

B. 使用关联元素

表单的 accessible name 则是从关联元素衍生而来。

<input type="checkbox" id="doggy">
<label for="doggy">狗勾认养意愿调查</label>

可以看见 <input> 的 id 属性,与 <label> 的 for 属性内容是一样的,这会元素之间制造出关联,让浏览器把 <label> 的文字内容,当作整个 checkbox 的 accessible name。

C. 使用 ARIA 属性

ARIA 属性之一写做 aria-label

ARIA 属性是另一个替代选项,会凌驾於原生 HTML 提供的 accessible name 之上,但可能会因此造成一些问题。

假设我们有以下元素,使用语音辨识工具的使用者,会无法看见该按钮的 accessible name(因为 accessible name 不再是 <button> 元素中的内容,而是被改成了 ARIA 属性的内容,ARIA 会凌驾於原生设定之上)进而无法正确的使用语音指令,选取到目标元素。

// 按钮的 accessible name 会是 "Add pizza to cart" 而非 "Add to cart"
<button aria-label="Add pizza to cart">Add to cart</button>

另一个属性写做 aria-labelledby

aria-labelledby 让我们把另一个元素的 id 指定成为当前元素的标签,在两个元素之间创造关系,辅助科技可以利用这个属性让使用者在元素之间切换。当你想要当作 accessible name 的文字位於页面的其他位置时,可以参考这种做法。


<input type="search" aria-labelledby="this">
<button id="this">Search</button>

附上:WAI-ARIA 的解决方案完整文件

https://www.w3.org/TR/wai-aria/#states_and_properties

注意

JSX 完全支援 ARIA 属性,只是 React 中大部分的 DOM 属性都写成 camelCased,而 ARIA 应该要保持 hyphen-cased 的写法。

<input
  type="text"
  aria-label={labelText}
  aria-required="true"
  onChange={onchangeHandler}
  value={inputValue}
  name="name"
/>

另外,上面提到的表单元素作法,在 JSX 中也支援,但是 for 属性在 JSX 中要写成 htmlFor。

<input id="namedInput" type="text" name="name"/>
<label htmlFor="namedInput">Name:</label>

  1. Keyboard Accessibility(可使用键盘操作的无障碍设计)

Keyboard Accessibility 是无障碍网页中最重要的一个面向。行动不便、无法稳定控制肌肉的使用者,会依赖键盘来使用网页。以下是几个实作重点:

Focus 的提示

键盘使用者使用 tab 按键在各个可互动的元素(例如:连结、按钮、输入框)之间切换,当一个元素被使用 tab 选到的时候,键盘会 "focus" 在这个元素上,并且可以操纵或触发这个元素

使用者需要视觉的提示来明白现在 focus 在哪个元素上,而浏览器会自动提供这个提示(通常是加上边框或者底色),而这些提示可以使用 CSS 元素隐藏。

因此,你应该要避免 outline:0 或 outline:none 的设定。此外,也可以额外添加 CSS 样式让 focus 的提示更加显眼,例如加上背景颜色。

元素互动的顺序

键盘使用者在不同元素中切换的时候,预设顺序会是遵循最自然的视觉惯性(由左到右、由上到下),在大多数的网页里,代表着元素切换的顺序会是 header、网页主要内容,最後是 footer。

这个顺序是参照页面的 source code,所以你应该确保网页架构不会让顺序乱掉,并且,留意使用 tabindex 的值不应大於 0,以避免去改变预设顺序。

tabindex 是什麽?用来代表该元素是否可以被 focused 以及在键盘切换中的位置:

// 代表这个元素不能被键盘切换到,通常用在当你有一个萤幕之外且不希望被选择到的内容
tabindex="-1"
// 代表这个元素是可被 focus 的,但顺序会在 tableindex = 1 or 2 or 3... 之後
// 另一个用法是,当你使用了自制的元素,没有预设顺序的状态下,可以加进此属性来确保元素可被 focus
tabindex="0"
// 大於零的状况,就代表元素顺序 e.g. tabindex="4" 的顺序在 tabindex="5" 之前并且在 tabindex="3" 之後
tabindex={positive value}

导览列与内容结构分明

键盘使用者则必须按下 Tab 或者其他按键来切换想要选取的物件,如果你的导览列内容太多、连结太多,会让使用者更加吃力。针对这个情况,有三种可以调整的方向:

  • 你可以提供 "跳到主要内容" 的连结,让使用者可以略过长长的导览列

  • 使用 Semantic Structure 设计的 heading

    使用 heading tag <h1> - <h6>,并且使用唯一的 <h1> 来描述整体网页,并渐次使用 <h2> - <h6> 来描述接下来的标题等内容,中间不应该跳过层级(例如 <h2> 的下一级直接跳到 <h4> 是不建议的做法),而 heading 作用以外的文字,就不要使用 heading tag 了。

  • 使用 Semantic Structure 设计的 region

    用来 <header> <nav> <main> <footer> 来区分出页面各区块,辅助科技就可以轻易的在这些页面区块之间切换。几个区块分配要点:一个页面有一个 <main><header><main><footer> 元素应该要是 <body> 的直接子元素(中间没有包其他任何东西);而 <nav> 元素应该只能被用在主要的导览列上面。

注意

React APP 的运作方式,在 runtime 的时候会不断地去修改 HTML DOM,有时候会造成键盘的 focus 功能失效,或者 focus 到一个未预期的元素上方,要修补这个漏洞,需要做一点手脚,把键盘的 focus 指引到正确的方向。我们需要使用 Refs 来重新设定 React App 的 focus 运作:

首先,使用 React.createRef() 创造一个 Ref,并且透过目标元素(这里是 <input>)上的 ref 属性,让 Ref 附属在元素中。我们创造出来的 Ref 通常会在元件被创建时,指派给一个 instance property,让这个 Ref 在这整个元件中都可以被 referenced。

我们建造出来的 Ref 会接收到底下的 DOM 元素,作为他的 current property。

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    return (
      <input
        type="text"
        ref={this.textInput
      />
    );
  }
}

接下来,我们就可以在元件的某个地方,使用 focus() 来 focus 我们 reference 的那个元素。

focus() {
  this.textInput.current.focus();
}

以上的案例是在同一个元件内的情况,有时候,是父元件会需要去 set focus 子元件上的元素,要达到这个目标,我们可以在子元件上设定特别的 prop,让父元件可以接触到子元件上的 DOM refs。

function CustomTextInput(props) {
  return (
		// 子元件绑定 ref 属性
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = React.createRef();
  }
  render() {
    return (
			// 建立出来的 Ref 被往下传到子元件中
      <CustomTextInput inputRef={this.inputElement} />
    );
  }
}

this.inputElement.current.focus();

如果你想更了解控制 focus 的技术,React 文件提供了一个很好的案例:

https://github.com/davidtheclark/react-aria-modal

参考资料

https://www.tpgi.com/what-is-an-accessible-name/

https://webaim.org/techniques/semanticstructure

https://reactjs.org/docs/accessibility.html


<<:  DAY 03 SCSS ? SASS ?

>>:  [Day 03] 一声探气,索性来资料分析 (探索性资料分析)

Day11 小实作-数字时钟

使用 React useState Hook 创建数字时钟 import React, {useSt...

[DAY11]文字与表情符号

改造的前提必然是要先了解程序的运作原理所以我们要先了解LINE提供给我们那些格式去使用 第一个先说到...

iOS APP 开发 OC 第六天, 方法的声明实现和调用

tags: OC 30 day 类事物不仅具有相同的特徵还具有相同的行为。 行为就是一个功能,C语言...

第二十一天:TeamCity 技术名词回顾

经过 20 天的练习,我们已经大致掌握了 TeamCity 的基本功能,刚好是一个很好的机会来回顾一...

【Day18】[资料结构]-堆积Heap-实作

堆积(Heap)建立的方法(以最大堆积实作) maxHeapify: 最大堆积化 push: 新增元...