iT邦幫忙

2021 iThome 鐵人賽

DAY 3
0
Modern Web

React 從 0.5 到 1系列 第 3

[鐵人賽 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


上一篇
[鐵人賽 Day02] 如何提升你的 React 網站易用性?(Web Accessibility)(上)- 無障礙網站 intro
下一篇
[鐵人賽 Day04] 如何提升你的 React 網站易用性?(Web Accessibility)(下)- Mouse and pointer events、Development Tools
系列文
React 從 0.5 到 115

尚未有邦友留言

立即登入留言