文章大綱與涵蓋範圍
繼上篇介紹完無障礙網站(Web Accessibility,又稱為 a11y)的目的與實踐方向,中、下篇將著重在 React Advanced Guide 裡提供的 Accessibility 指南。
在可以廣泛運用的 Accessibility 實作知識上,hightlight React 對此的支援(例如 JSX 是否也能直接應用某些 HTML 屬性?)或者調整(某些屬性在 React 裡,可能有寫法的差異)。
下篇會使用一個案例,介紹滑鼠點擊事件的實作,並且使用 React 生態系中的測試工具測試效果。
實作 Mouse and pointer events 的 Accessibility 優化
請想像一個需求:在畫面上,有三個按鈕,第一個按鈕點擊之後,會 pop up 出一個子目錄列表,這個列表需要可以關掉,第二三個按鈕點擊之後,會跳出 alert 視窗(但在這個案例我們不用關注二跟三)。
你會怎麼設計這個元件呢?以下是一個很直觀,但其實對於鍵盤使用者不太友善的做法,你是否也看出端倪了呢?原本的 React 文件使用 class component,這裡我用 hook 稍加改寫。
首先,讓我們把元件上的按鈕先畫出來,並且設定好列表 pop up 的狀態,再加上點擊按鈕之後會展開列表的函式。
export const ClickExample = () => {
const [isShowOption, setIsShowOption] = useState(false)
const onClickHandler = () => {
setIsShowOption(true)
}
return (
<>
<div>
<button onClick={onClickHandler}>Select an option</button>
{isShowOption && (
<ul>
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
</ul>
)}
</div>
<button>Another button</button>
<button>The other button</button>
</>
)
}
加入了一些排版之後,此時的畫面看起來:
接下來,我們要加入一個點擊空白處,就可以把列表關起來的方法。在這裡新增了一個 ref ,並指向底下的子目錄列表外層元素,讓我們可以獲得 current 的狀態。當點擊範圍不在列表元素的 current 範圍裡的時候,就收合列表。
export const ClickExample = () => {
//..
const toggleContainer = useRef(null);
useEffect(() => {
const onClickOutsideHandler = (event) => {
if (isShowOption && !toggleContainer.current.contains(event.target)) {
setIsShowOption(false);
}
}
window.addEventListener('click', onClickOutsideHandler);
return () => {
window.removeEventListener('click', onClickOutsideHandler);
};
});
return (
<Container>
<div ref={toggleContainer}>
//..
)}
此時的畫面看起來:
用滑鼠控制的情況下,還算順利。然而當使用者用鍵盤時,就會遇到問題。(以下的畫面鍵盤順序為:使用 tab 切換到第一個按鈕,按下 enter 打開子目錄,接下來按 enter 或是 tab 都無法把子目錄關上)。
此時,可以利用別的 event handlers 事件來改善這個情境,拿掉點擊空白處關閉列表的設定。可以設計成偵測使用者的滑鼠、Focus 的狀態,如果已經離開了按鈕一一段時間,就自動關上列表。如此一來,不管是鍵盤或是滑鼠的使用者,可以順暢的操作畫面。
所以在這裡把原本的 ref 以及 event listener 拿掉,偵測按鈕,當別的按鈕被 focus 而按鈕一的 focus 狀態解除時,就把子目錄列表關上。這裏使用 setTimeout 的原因是:blur event 會比新的 focus event 更早觸發,而我們必須要先去確認別的按鈕是否被 focus 才能決定是否要關上列表。
export const ClickExample = () => {
//..
let timeOut
const onBlurHandler = () => {
timeOut = setTimeout(() => {
setIsShowOption(false);
})
}
const onFocusHandler = () => {
clearTimeout(timeOut);
}
return (
// React assists us by bubbling the blur and focus events to the parent.
<Container>
<div onBlur={onBlurHandler} onFocus={onFocusHandler}>
//..
)
}
經過調整之後,畫面的互動看起來像是這樣:
使用滑鼠
使用鍵盤
JSX 的無障礙網頁 Development Tools
Crete React App 會自動裝載 eslint-plugin-jsx-a11y 這個套件(所以 Create Next App 也是有的)。
如果你想要引入更多的 accessibility 規則提示,可以調整你的 .eslintrc
檔案。你可以到這裡 https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#supported-rules 來看看它支援的規則。
加上去之後,就可以看到相關提示了!例如下圖是在說 <img>
元素必須有個 alt 屬性來說明內容。
這一張則是跟表單有關的設定。
關於這些規則,套件裡的文件也都寫得相當清楚。
如果覺得要改善自己的專案 Accessibility 毫無頭緒,不妨安裝這個套件開始,從提示反查去了解各項規範。