前端無(wú)障礙開(kāi)發(fā)指南
作者 | 孫郁儼
The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect," said Tim Berners-Lee, W3C Director and inventor of the World Wide Web.
30年前,Tim Berners-Lee 在歐洲核子研究中心創(chuàng)建了第一個(gè) Web 網(wǎng)頁(yè),宣告了萬(wàn)維網(wǎng)的誕生。自此,萬(wàn)維網(wǎng)就承載著開(kāi)放平等的愿景。
Accessibility——無(wú)障礙設(shè)計(jì)&信息無(wú)障礙(也簡(jiǎn)稱為 A11y),雖然常常會(huì)被解釋為”為殘障人士服務(wù)“,但其無(wú)障礙設(shè)計(jì)的核心在于為所有人提供同等的體驗(yàn)。我們每個(gè)人都有可能在某些時(shí)刻成為失能者,這稱為場(chǎng)景性殘疾(situational disability & temporary disability),比如受傷骨折后,暫時(shí)失去了部分活動(dòng)能力。又比如被強(qiáng)光照射時(shí),看不清楚事物,在嘈雜地鐵中的聽(tīng)力產(chǎn)生障礙等等。
根據(jù) W3C 組織的定義,Web accessibility 意味著每個(gè)人都可以感知、理解并與 Web 交互,甚至為 Web 做出貢獻(xiàn)。中國(guó)工信部也指出,信息無(wú)障礙是指通過(guò)信息化手段彌補(bǔ)身體機(jī)能、所處環(huán)境等存在的差異,使任何人(無(wú)論是健全人還是殘疾人,無(wú)論是年輕人還是老年人)都能平等、方便、安全地獲取、交互、使用信息。
但在萬(wàn)物互聯(lián)的當(dāng)下,盡管我們的衣食住行早已與網(wǎng)絡(luò)世界息息相關(guān),互聯(lián)網(wǎng)并未成一個(gè)平等的,人人都可以訪問(wèn)的世界。根據(jù)2022 年 The WebAIM Million 統(tǒng)計(jì)報(bào)告,在對(duì) 100萬(wàn) 個(gè)網(wǎng)站首頁(yè)進(jìn)行無(wú)障礙分析后,得到的結(jié)果卻差強(qiáng)人意:
- 在 100 萬(wàn)個(gè)首頁(yè)中,一共檢測(cè)到 50,829,406 項(xiàng)非重復(fù)的無(wú)障礙錯(cuò)誤,平均每個(gè)首頁(yè)有50.8個(gè)錯(cuò)誤。
- 96.8% 的首頁(yè)檢測(cè)到了 WCAG 2 錯(cuò)誤,未能達(dá)到 WCAG 2的標(biāo)準(zhǔn),盡管現(xiàn)在最新的標(biāo)準(zhǔn)是WCAG 2.1
- 在殘障用戶的頁(yè)面訪問(wèn)流程中,每交互 19 個(gè)首頁(yè)元素,就可能遇到一個(gè)無(wú)障礙錯(cuò)誤
圖源:2022 年 The WebAIM Million 報(bào)告
在這些頁(yè)面無(wú)障礙錯(cuò)誤中,96.5%的錯(cuò)誤歸屬于以下五類:
- 頁(yè)面顏色對(duì)比度不達(dá)標(biāo),影響視力障礙用戶的訪問(wèn)體驗(yàn)。
- 未定義<img />標(biāo)簽的alt屬性,影響輔助技術(shù)(Assistive technologies, ATs) 如屏幕閱讀器等設(shè)備獲取圖片信息。
- 空鏈接和空按鈕,指不包含不包含實(shí)際的文本的<a>標(biāo)簽或 <button>標(biāo)簽。這些標(biāo)簽只包含一個(gè)圖像或一個(gè)文本的圖像,會(huì)導(dǎo)致使用 ATs 設(shè)備的用戶無(wú)法感知可交互元素的實(shí)際用途。
- 表單元素<input />標(biāo)簽沒(méi)有對(duì)應(yīng)的<label>標(biāo)簽。</label><label>標(biāo)簽對(duì)于 ATs 設(shè)備至關(guān)重要。沒(méi)有它,用戶將無(wú)法感知他們?cè)谂c哪個(gè) <input /> 標(biāo)簽交互。
- <html>標(biāo)簽沒(méi)有設(shè)置lang屬性。不同的語(yǔ)言類型在屏幕閱讀器中的發(fā)音是不同的,比如six單詞在法語(yǔ)和英文兩種類型的屏幕閱讀器中的發(fā)音就非常的不同。在</html><html>上定義lang屬性,會(huì)告知 ATs 設(shè)備當(dāng)前頁(yè)面所使用的語(yǔ)言。
作為前端開(kāi)發(fā)者,我們要如何把關(guān)頁(yè)面的無(wú)障礙功能呢?
在前端開(kāi)發(fā)的視角中,每一個(gè) Web 應(yīng)用都可以拆解為 HTML、CSS 和 JavaScript。HTML 會(huì)經(jīng)過(guò) HTML Parser 將 HTML 結(jié)構(gòu)轉(zhuǎn)換成 DOM Tree;CSS 會(huì)經(jīng)過(guò) CSS Parser 將 CSS 轉(zhuǎn)換成 CSSOM Tree。最終,瀏覽器根據(jù) DOM Tree 和 CSSOM Tree 構(gòu)建出最終的 Render Tree。
對(duì)于無(wú)障礙 Web 應(yīng)用,除了包含 DOM 和 CSSOM 之外,將包含 AOM (Accessibility Tree,可訪問(wèn)性樹(shù))。AOM 可訪問(wèn)性樹(shù)和 DOM 樹(shù)平行存在。簡(jiǎn)單來(lái)說(shuō),可訪問(wèn)性樹(shù)是 DOM 樹(shù)的一個(gè)子集。每個(gè)需要暴露給 ATs 輔助技術(shù)的 DOM 元素都對(duì)應(yīng)一個(gè)在可訪問(wèn)樹(shù)中存在的無(wú)障礙對(duì)象。定義 AOM 實(shí)現(xiàn)的標(biāo)準(zhǔn)是 WAI-ARIA(Web Accessibility Initiative – Accessible Rich Internet Application),即可訪問(wèn)的互聯(lián)網(wǎng)富應(yīng)用標(biāo)準(zhǔn),致力于解決應(yīng)用的可訪問(wèn)性問(wèn)題,它與HTML5 標(biāo)準(zhǔn)同屬于 W3C 組織。Web 應(yīng)用的 AOM 也并非遙不可及,打開(kāi) Chrome 瀏覽器的 Devtools,我們即可查看頁(yè)面的 AOM 結(jié)構(gòu)。
在了解了無(wú)障礙的基本概念后,我們分別從 HTML、開(kāi)發(fā)框架以及 CSS等角度,一起來(lái)看看無(wú)障礙頁(yè)面的實(shí)現(xiàn)方式吧。
編寫 HTML 時(shí)需要考慮的 Web Accessibility
就像瀏覽器引擎依賴 HTML 結(jié)構(gòu)以構(gòu)建頁(yè)面 UI 骨架,ATs 設(shè)備也依賴 HTML 結(jié)構(gòu)來(lái)構(gòu)建頁(yè)面的 AOM 可訪問(wèn)性樹(shù)。所以語(yǔ)義化的 HTML 對(duì)于實(shí)現(xiàn) Web 應(yīng)用無(wú)障礙至關(guān)重要,因?yàn)樵?HTML 標(biāo)簽中包含了構(gòu)建 AOM 的必要元數(shù)據(jù)。
參考上圖,ATs 設(shè)備完全可以正確地渲染滑動(dòng)輸入框,即便我們沒(méi)有在HTML 標(biāo)簽上添加 WAI-ARIA 屬性。但我們?cè)陂_(kāi)發(fā)時(shí)往往會(huì)忽略 HTML 元素的實(shí)際語(yǔ)意,而更多采用無(wú)語(yǔ)意的 <div> 和 <span> 標(biāo)簽 (<div> 和 <span> 之外的近 104 個(gè) HTML 標(biāo)簽都具有語(yǔ)義信息)。因?yàn)檫@兩個(gè)標(biāo)簽沒(méi)有默認(rèn)樣式,足夠簡(jiǎn)單,就像白紙一樣可以隨意畫(huà)上 CSS 樣式。但這樣的標(biāo)簽,對(duì)于 ATs 設(shè)備來(lái)說(shuō),就是災(zāi)難。
以上圖為例,對(duì)于 <button> FOO </button> 標(biāo)簽,讀屏軟件將讀出 “Button, foo",告知用戶當(dāng)前元素是按鈕,包含文字 foo。但對(duì)于 <div class="button"> FOO </div> 標(biāo)簽,讀屏軟件只能讀出 “foo”,并不能提示當(dāng)前元素是一個(gè)可交互的按鈕。雖然我們也可以通過(guò)設(shè)置 WAI-ARIA 屬性為 HTML 標(biāo)簽增添無(wú)障礙語(yǔ)意,比如 <div class="button" role="button"> FOO </div>,但這樣會(huì)平添許多額外的工作,也增加了出錯(cuò)的機(jī)率:根據(jù) The WebAIM Million 統(tǒng)計(jì)報(bào)告,包含 ARIA 的頁(yè)面比不使用 ARIA 的頁(yè)面,檢測(cè)出無(wú)障礙性錯(cuò)誤的可能高 70%。
通過(guò) HTML 提升頁(yè)面可訪問(wèn)性
規(guī)則 1:結(jié)構(gòu)和樣式分離
在社區(qū)中一直都有人在提倡 CSS裸奔日(CSS Naked Day),編寫 HTML 時(shí)不要基于 UI 視覺(jué)效果(CSS 樣式),而是基于 UI 的頁(yè)面結(jié)構(gòu),可以確保 HTML 的語(yǔ)義完善,增強(qiáng)頁(yè)面可訪問(wèn)性。
相關(guān)瀏覽器插件:HeadingsMap - Chrome Web Store
規(guī)則 2:只在必要時(shí)使用 ARIA
WAI-ARIA 的全稱是 Accessible Rich Internet Applications,簡(jiǎn)稱 ARIA,是 W3C規(guī)范之一。ARIA 允許 Web 開(kāi)發(fā)者創(chuàng)建只有 ATs 技術(shù)(比如屏幕閱讀器)可以看到的內(nèi)容(屬性),用以實(shí)現(xiàn) HTML 無(wú)法達(dá)成的無(wú)障礙功能,比如:
- 增強(qiáng)交互式控件的可訪問(wèn)性,比如下拉菜單、彈窗,滑塊等
- 為頁(yè)面結(jié)構(gòu)定義有用的地標(biāo)
- 定義動(dòng)態(tài)更新的“活動(dòng)區(qū)域”
- 改善鍵盤可訪問(wèn)性和交互性
ARIA 表現(xiàn)為 HTML 的屬性,確定了元素的 ARIA 角色、狀態(tài)和屬性。這些信息幫助 ATs 技術(shù)更好地理解 Web 頁(yè)面,確保用戶與頁(yè)面元素的交互。一般情況下,ARIA 不會(huì)影響 Web 頁(yè)面的渲染,也不會(huì)影響鼠標(biāo)或鍵盤用戶的行為,只有使用輔助技術(shù)的用戶才能感知到 ARIA。開(kāi)發(fā)人員隨意使用 ARIA 所導(dǎo)致的問(wèn)題,對(duì)于頁(yè)面無(wú)障礙功能往往是致命的,而且難以察覺(jué)。
但 ARIA 永遠(yuǎn)無(wú)法替代語(yǔ)義化 HTML 標(biāo)簽,NO ARIA is better than bad ARIA。請(qǐng)優(yōu)先考慮語(yǔ)義最貼近的 HTML 標(biāo)簽,只在必要時(shí)使用 ARIA。
相關(guān)瀏覽器插件:
- Visual ARIA - Chrome Web Store
- Landmark Navigation via Keyboard or Pop-up - Chrome Web Store
規(guī)則 3:提升表單結(jié)構(gòu)的包容性
(1) 采用 <fieldset> 為表單項(xiàng)分類
當(dāng)表單分為不同板塊時(shí),我們可能會(huì)使用 <div> 元素實(shí)現(xiàn)表單項(xiàng)的樣式板塊劃分,但這樣的劃分并不利于無(wú)障礙設(shè)備獲得表單項(xiàng)信息,可以使用<fileset>進(jìn)行替換。
(2) 正確使用 label,為 <input /> 標(biāo)簽設(shè)置對(duì)應(yīng)的 label
在實(shí)現(xiàn)表單時(shí),我們往往會(huì)通過(guò) placeholder 來(lái)提示當(dāng)前表單項(xiàng)的填寫內(nèi)容。這樣的設(shè)計(jì)會(huì)導(dǎo)致當(dāng) input 得到焦點(diǎn)時(shí),placeholder 自動(dòng)消失,造成用戶無(wú)法感知當(dāng)前表單項(xiàng)的內(nèi)容。我們可以使用 label 充當(dāng) placeholder,這樣的交互方式也稱為 Float Label Pattern
(3) 盡可能使用原生的表單元素
在制作表單組件時(shí),我們往往會(huì)出于實(shí)現(xiàn) UI 樣式的要求,采用 <div> 替代原生的表單元素。盡管這些表單組件在視覺(jué)和功能上滿足了 UI 要求,但它們并未實(shí)現(xiàn)原生表單元素的無(wú)障礙功能。
(4) 為表單元素設(shè)置原生的校驗(yàn)屬性
required、minlength、pattern 等表單的原生校驗(yàn)屬性,不但可以滿足正常的表單校驗(yàn)需求,也具有更好的無(wú)障礙支持
規(guī)則 4:注意頁(yè)面的焦點(diǎn)管理,允許用戶僅通過(guò)鍵盤完成交互
很多行動(dòng)不便的用戶依賴鍵盤操作,靠 Tab 鍵和方向鍵等瀏覽網(wǎng)。因此我們?cè)跇?gòu)建 Web 應(yīng)用的時(shí)候要注意:
- 確保頁(yè)面所有內(nèi)容都可以通過(guò)鍵盤訪問(wèn)
- 盡可能地提供鍵盤快捷鍵交互
- 避免設(shè)計(jì)只在鼠標(biāo) hover 時(shí)才會(huì)被激活的元素
一些 HTML 的原生標(biāo)簽具備可聚焦屬性,也被稱為可聚焦元素。這些原生 HTML 元素,天然存在于頁(yè)面 Tab 鍵順序內(nèi),內(nèi)置了鍵盤事件處理,可以通過(guò) Tab 鍵聚焦,并且獲得焦點(diǎn)時(shí)有可見(jiàn)的焦點(diǎn)指示器(往往是顯眼的藍(lán)色框框)。但對(duì)于無(wú)法聚焦的元素,我們可以設(shè)置元素的 tabindexlace 屬性,使元素可聚焦。
如果想給當(dāng)前元素生成快捷鍵的話,可以給元素設(shè)置 accesskey 屬性。但使用 accesskey也需注意以下問(wèn)題:
- accesskey 值可能與系統(tǒng)或?yàn)g覽器快捷鍵或輔助技術(shù)功能相沖突
- 當(dāng)考慮頁(yè)面國(guó)際化時(shí),某些 accesskey 值可能不會(huì)出現(xiàn)在一些鍵盤上
- 依賴于數(shù)值的 accesskey 可能會(huì)讓具有認(rèn)知障礙的用戶感到困惑,因?yàn)閿?shù)值和觸發(fā)的功能并沒(méi)有邏輯聯(lián)系
- 如果沒(méi)有告知用戶快捷鍵的存在,那么可以會(huì)造成用戶誤觸
相關(guān)瀏覽器插件:
- taba11y - Chrome Web Store
- NerdeFocus - Chrome Web Store
規(guī)則 5:定義文檔的語(yǔ)言類型
在 <html> 標(biāo)簽元素上設(shè)置正確的 lang 屬性。如果你的頁(yè)面沒(méi)有顯式設(shè)置當(dāng)前頁(yè)面所使用的語(yǔ)言,那么讀屏軟件將無(wú)法選擇匹配的語(yǔ)音配置文件和字符集,讀屏軟件讀出的頁(yè)面內(nèi)容是亂碼。所以,為了確保頁(yè)面的內(nèi)容正確,請(qǐng)務(wù)必為 </html><html> 元素指定有效的BCP 47語(yǔ)言。
規(guī)則 6:為 <img /> 添加 alt 屬性,明確鏈接和按鈕的信息
往往一張表情包圖片就可以抵千言萬(wàn)語(yǔ),但對(duì)于讀屏軟件來(lái)說(shuō),讀取 <img /> 標(biāo)簽的有效信息,只能靠 alt 屬性。所以不要忘記為 <img /> 標(biāo)簽添加描述性的 alt 屬性。如果圖片只是為了裝飾效果,那么可以考慮將 <img /> 標(biāo)簽 替換為 CSS 背景圖。
與 <img />標(biāo)簽類似,讀屏軟件對(duì)于 <a> 和 <button> 標(biāo)簽的信息獲取,依賴于標(biāo)簽包裹的文本。使用”閱讀更多“,甚至圖片作為這類標(biāo)簽的包裹內(nèi)容,并不能為用戶提供足夠的信息。如果不方便添加文本信息,也可以利用 aria-label 增強(qiáng)元素的語(yǔ)義信息:
<a href="post.php?post=632" aria-label="More on Using Meaningful Link Text">More...</a>
使用前端框架需要考慮的 Web Accessibility
根據(jù) 2022 年 The WebAIM Million 統(tǒng)計(jì)報(bào)告,使用 JavaScript 框架的頁(yè)面比不使用框架的頁(yè)面存在更多的無(wú)障礙錯(cuò)誤,其中 React 開(kāi)發(fā)的頁(yè)面平均存在 50.8 個(gè)錯(cuò)誤,Vue 開(kāi)發(fā)的頁(yè)面存在 63.4 個(gè)錯(cuò)誤。雖然統(tǒng)計(jì)結(jié)果不能說(shuō)明框架導(dǎo)致了這些錯(cuò)誤,但在使用框架進(jìn)行 Web 開(kāi)發(fā)時(shí),常常會(huì)忽略使用 HTML 原生標(biāo)簽,或者引入無(wú)障礙功能支持性不佳的組件庫(kù),導(dǎo)致框架開(kāi)發(fā)的 Web 應(yīng)用可訪問(wèn)性普遍較差。
提升前端框架的無(wú)障礙支持性
規(guī)則 1:使用語(yǔ)義化 HTML 標(biāo)簽,完善 HTML 標(biāo)簽的屬性
規(guī)則 2:在設(shè)計(jì)組件時(shí)考慮整體的 HTML 結(jié)構(gòu)
維護(hù)層級(jí)明晰的 HTML 結(jié)構(gòu),對(duì)于 Web 應(yīng)用的無(wú)障礙功能十分重要。因?yàn)?ATs 軟件,特別是讀屏軟件,不止是由上至下地展現(xiàn)頁(yè)面信息,更會(huì)基于頁(yè)面不同級(jí)別的標(biāo)題或者文檔地標(biāo)元素進(jìn)行頁(yè)面導(dǎo)航。在將頁(yè)面拆分成不同組件后,保持 HTML 文檔結(jié)構(gòu)層級(jí)會(huì)更加復(fù)雜。比如當(dāng)一個(gè)組件包含 <h2> 標(biāo)簽時(shí),可能在一些位置該組件會(huì)破壞原有 HTML 文檔結(jié)構(gòu)。
規(guī)則 3:避免使用無(wú)意義的 HTML 標(biāo)簽
在使用 React、Vue 等框架時(shí),我們往往需要將組件包裹在一個(gè)根元素中:
但這樣的處理在編譯后,會(huì)在造成元素結(jié)構(gòu)的混亂:
<div> 標(biāo)簽混在 <tr> 標(biāo)簽中,會(huì)導(dǎo)致讀屏軟件無(wú)法正確解析 table ,造成用戶無(wú)法訪問(wèn)表格內(nèi)容。此時(shí),我們應(yīng)該使用 React 的 <fragment> 標(biāo)簽(Vue 中可以使用 vue-fragment),確保根元素不存在于最終的 DOM 結(jié)構(gòu)內(nèi):