文章大纲与涵盖范围
继上篇介绍完无障碍网站(Web Accessibility,又称为 a11y)的目的与实践方向,中、下篇将着重在 React Advanced Guide 里提供的 Accessibility 指南。
在可以广泛运用的 Accessibility 实作知识上,hightlight React 对此的支援(例如 JSX 是否也能直接应用某些 HTML 属性?)或者调整(某些属性在 React 里,可能有写法的差异)。
实作 Accessibility 的重点提示
accessible name 被写在 HTML 元素上,用途在为辅助科技提供更多识别元素的资讯,举个应用场境的例子:使用语音识别工具的人,可以用 accessible name 来锁定想要选去的元素;使用萤幕阅读工具的人,accessible name 让他更理解该元素的意涵。
使用语音识别工具的人,可能会说 “Click Doggy Day Care link“ 来触发 <a>
元素。
<a href="doggy.html">Doggy Day Care</a>
从上面的案例中可以看到,并非每个元素都需要特别添加更多资讯,以下举出三项 accessible name 的实作方式 :
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>
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] 一声探气,索性来资料分析 (探索性资料分析)
使用 React useState Hook 创建数字时钟 import React, {useSt...
改造的前提必然是要先了解程序的运作原理所以我们要先了解LINE提供给我们那些格式去使用 第一个先说到...
tags: OC 30 day 类事物不仅具有相同的特徵还具有相同的行为。 行为就是一个功能,C语言...
经过 20 天的练习,我们已经大致掌握了 TeamCity 的基本功能,刚好是一个很好的机会来回顾一...
堆积(Heap)建立的方法(以最大堆积实作) maxHeapify: 最大堆积化 push: 新增元...