博客 / 詳情

返回

vivo 場景下的 H5無障礙適配實踐

作者:vivo 互聯網前端團隊- Zhang Li、Dai Wenkuan

隨着信息無障礙的建設越來越受重視,開發人員在無障礙適配中也遇到了越來越多的挑戰。本文是筆者在vivo開發H5項目做無障礙適配的實踐總結。本文主要介紹了在前端項目中常用的無障礙手勢和無障礙屬性,並且結合具體的開發案例為開發者真實展示了適配要點,提供組件適配思路。希望本文能為前端開發者帶來更多的參考和幫助。

一、背景

1.1 無障礙適配認知

無障礙(Accessibility)是指為各種能力水平的人們提供公平和平等的機會和體驗,以便他們可以更容易地訪問、使用和參與社會中的各種產品、服務和環境。這些人包括身體殘疾、聽力、視覺、認知和學習障礙等各種能力水平的人。

1.2 無障礙適配原因

常見的障礙類型包括:視覺障礙、聽覺障礙、認知障礙、行動障礙。這些“有障礙”的羣體,在使用軟件時無法像普通人一樣操作,他們或許看不清,需要更大的字體,或許看不到,需要語音播報,又或許聽不清聽不到,需要依賴視覺反饋,或許肢體操作不方便,無法自由操縱手機。

所以,國家和企業需要站在“有障礙”的羣體思考,為各種能力水平的人們提供公平和平等的機會和體驗。

  • 【政策】:2011年《中國殘疾人事業“十二五”規劃綱要》指出了, 建設無障礙環境的主要任務之 一就是加強信息無障礙建設,並明確了相關的政策措施。
  • 【包容性】:無障礙適配是一個重要的設計和開發目標,可以幫助您的網站或應用程序更加包容和可訪問。
  • 【經濟性】:無障礙適配不僅是一種道德義務,也是一種經濟利益,因為它可以為您的網站帶來更廣泛的受眾和更好的用户體驗,帶動業務增長。

1.3 無障礙適配引導

除了系統默認支持的大字體、內容播報、語音識別、字幕、語音控制等基礎無障礙適配,更多的頁面交互還是需要前端工程師們完成適配,所以開發者們需要開發出可識別的web頁面,讓障礙羣體可以正常訪問和使用。本文主要針對視障人羣進行無障礙適配。

二、無障礙操作

在無障礙開發前,前端工程師們需要了解下無障礙的操作手勢,站在障礙人羣的角度體驗操作,這樣才能開發出更好的交互體驗。也能避免因操作錯誤導致開發錯誤和重複開發。

2.1 讀屏幕軟件

首先我們需要了解下針對視力障礙人員使用的讀屏設備或軟件,常見的讀屏軟件,如下列表:

圖片

由於我們是在vivo手機內做移動端安卓手機無障礙適配,以下的方案、案例等介紹均是基於Android-TalkBack。

2.2 常用操作手勢

藉助 TalkBack 手勢,可以在 Android 設備上進行導航和執行常用操作,以下操作為安卓9.1版及更高版本,僅在vivo手機的操作。官方鏈接:Talkback 手勢。

圖片

三、常用屬性介紹

3.1 aria屬性

ARIA全稱“Accessible Rich Internet Applications(可訪問的富互聯網應用)”,是W3C的Web無障礙推進組織在2014年3月20日發佈的可訪問富互聯網應用實現指南。是一個為殘疾人士等提供無障礙訪問動態、可交互Web內容的技術規範。ARIA 用於提高使用 HTML、CSS、JavaScript、AJAX 和相關技術開發的動態內容和高級 UI 控件的輔助功能。ARIA 現在正式成為 HTML5 規範的一部分,還可以嵌入在常用的 JavaScript 庫中。

圖片

3.2 aria狀態

圖片

3.3 role屬性

role將元素標記為不同的屬性,常用屬性“button(按鈕),region(圖形)”,根據定義的不同屬性,播報不同的內容。

圖片

3.4 tabindex 屬性

tabindex 屬性的使用可以使得原本無法取得焦點的元素獲取焦點。目的是為了使用户可以用鍵盤訪問任何可以用鼠標訪問的元素。我們知道,使用 Tab 鍵可以按文檔順序 tab 到所有可以獲取焦點的元素。tabindex 可以設置為 -1, 0 或者是任何自然數。

圖片

當用户使用 Tab 鍵瀏覽頁面時,元素獲取焦點的順序是按照 HTML 代碼裏面元素出現的順序排列的,有時跟實際看到的頁面順序並不一致。

四、無障礙適配案例

4.1 項目適配案例

1.語言設置

在全局<html>元素中的 lang 屬性設置頁面的語言:

英文:

<html lang="en"> 

中文:

<html lang="zh-CN"></html>

國內的中文項目一般需要設置為中文,如果設置成了英文,有些數字會播報成英文。

2.層級屬性

如果項目還沒開始適配,則開啓無障礙播報,會發現標籤div和span默認作為焦點並播報,這樣的話焦點和播報內容就非常分散。

如下圖2,就會有3個焦點,這樣過於分散,一般適配會將ui的模塊作為一個大焦點整體播報,此時在外層div添加tabindex=1聚焦。

圖片

圖1:原圖

圖片

圖2:分散焦點

圖片

圖3:一個大焦點

備註:綠色框為焦點標記

(1)聚焦播報

因為div標籤會自動播報,所以就算焦點聚焦到外層,但是內層還是會自動播報。

<div class="content"  tabindex="1">
  <div class="amount">
    <div class="num">100</div>
    <div class="unit">元</div>
  </div>
  <div class="desc">話費充值</div>
</div>
播報內容:100元話費充值。

(2)自定義播報

但是會存在div裏寫的內容和需要播報的內容不一致,則可在外層tabindex="1"聚焦後,通過aria-label寫上自定義內容

<div class="content" tabindex="1" aria-label="獲得100元的話費充值券">
  <div class="amount">
    <div class="num">100</div>
    <div class="unit">元</div>
  </div>
  <div class="desc">話費充值</div>
</div>
播報內容:獲得100元話費充值券

3.聚焦樣式

聚焦可通過tabindex實現,但是聚焦後樣式會有黃色的邊框,可通過outline: none去除,但是頁面中的焦點太多,可通過全局去除。

在公共css文件裏設置:

*[tabindex] {
    outline: none;
}

4.圖片播報

(1)圖片的alt屬性設置

圖片通過img標籤實現,如果圖片不可聚焦,則設置**alt=""**即可。如果可聚焦並需要播報內容,建議通過aria-label設置。如果使用alt,一旦圖片加載不出來,就會把alt的內容顯示出來,而且alt內容沒有樣式,在H5頁面上會顯得很突兀。

<img src="close.png" aria-label="關閉" alt=""/>
<img src="dog.png" alt=""/>

(2)去除圖片默認屬性(圖形)播報

可在img標籤裏,將role屬性改為row

<img
  src="title.png"
  tabindex="1"
  aria-label="超級會員送您1個紅包"
  role="row"
/>

5.按鈕播報

通常ui上的按鈕在選中後,需要播報按鈕內容+按鈕+引導操作(如:點按兩次即可激活)

首先需要聚焦(tabindex="1")到節點,然後需要在按鈕上的節點上寫上role="button",加上這個屬性後,後面會自動播報“按鈕,點按兩次即可激活”

案例:div中的按鈕

圖片

<div tabindex="1" role="button" class="btn">立即使用</div>
播報內容:立即使用,按鈕,點按兩次即可激活

案例:img中的按鈕

圖片

<img
  class="btn"
  tabindex="1"
  role="button"
  aria-label="開"
  src="open.png"
/>
播報內容:開,按鈕,點按兩次即可激活

6.數字播報

如:手機號:181**8805中的星號不能正確播報,而是播報成乘,數字播報成整數。

錯誤播報:一百八十一乘乘乘乘八八零五

需正確播報:幺八幺星號星號星號星號八八零五

可寫一個公共方法映射數字、*號和X號播報文案,在需要的轉換方法裏調用該方法形成手機號播報文案。

公共方法如下:

function $broadcastNumber(number) {
  if (number === 0) return '零'
  if (!number) return ''
  const numberMap = ['零', '幺', '二', '三', '四', '五', '六', '七', '八', '九']
  const specialMap = { '*': '星號', 'X': '叉號', 'x': '叉號' }
  return number.toString().split('').map(item => numberMap[item] || specialMap[item] || item).join('')
}

7.自定義事件播報

目前帶有點擊事件的節點,聚焦後會默認播報點按兩次即可激活(系統規範),但是這個引導不夠明確,需要就具體交互場景制定播報內容,如:點按兩次即可選中,點按兩次即可打開紅包等。

方案:在click事件節點裏層包裹div,並將焦點tabindex寫在這一層的div上,再自定義播報的內容即可。

<div class="content" tabindex="1" @click="select">
  <div class="amount">
    <div class="num">100</div>
    <div class="unit">元</div>
  </div>
  <div class="desc">話費充值</div>
</div>
播報:100元話費充值,點按兩次即可激活
<div class="content" @click="select">
  <div class="container" tabindex="1" aria-label="獲得100元的話費充值券,點按兩次即可選中">
    <div class="amount">
      <div class="num">100</div>
      <div class="unit">元</div>
   </div>
  <div class="desc">話費充值</div>
 </div>
</div>
播報:獲得100元話費充值券,點按兩次即可選中

8.額外內容播報

如:第幾項,共幾項,點按兩次即可選中

圖片

方案:可用空div加aria-label實現

<div class="content" @click="select">
  <div class="container" :class="{ 'z-active' : code === item.code }" tabindex="1">
    <div v-if="code === item.code" aria-label="已選中"></div>
    <div class="amount">
      <div class="num">{{ item.num }}</div>
      <div class="unit">元</div>
    </div>
    <div class="desc">{{ item.desc }}</div>
    <div :aria-label="`第${index+1}項,共${list.length}項,${code === item.code? '' : '點按兩次即可選中'}`"></div>
  </div>
</div>

9.整體播報

通常整體播報的內容較多,且播報順序非代碼書寫順序,這個時候就需要在外層焦點裏,控制播報的內容,主要可通過兩種方法實現,aria-label拼接參數和aria-labelledby設置id。

圖片

需要播報的內容:話費充值券,50元滿減券,滿199元可使用,立即使用。

(1)aria-label拼接參數

可通過在外層節點設置tabindex=1後,再添加aria-label屬性按照需要的順序添加參數

案例:

<div class="content" tabindex="1" :aria-label="`${title}${num}元${type}${rule}${btn}`">
  <div class="left">
    <div class="amount">
      <span class="num">{{ num }}</span>
      <span class="unit">元</span>
    </div>
    <div class="type">{{ type }}</div>
  </div>
  <div class="right">
    <div class="desc">
      <div class="title">{{ title }}</div>
      <div class="rule">{{ rule }}</div>
    </div>
    <div role="button" class="btn">{{ btn }}</div>
  </div>
</div>

(2)aria-labelledby設置id

在外層節點設置tabindex=1後,在需要播報的內容節點裏添加id值,並將id值按照需要的順序寫在外層節點aria-labelledby屬性裏

案例:

<div class="content" tabindex="1" aria-labelledby="title amount type rule btn">
  <div class="left">
    <div id="amount" class="amount">
      <span class="num">{{ num }}</span>
      <span class="unit">元</span>
    </div>
    <div id="type" class="type">{{ type }}</div>
  </div>
  <div class="right">
    <div class="desc">
      <div id="title" class="title">{{ title }}</div>
      <div id="rule" class="rule">{{ rule }}</div>
    </div>
    <div id="btn" role="button" class="btn">{{ btn }}</div>
  </div>
</div>

10.局部特殊播報

如優惠券模塊,可整體選中播報全部優惠券內容,但內部立即使用按鈕又可聚焦播報跳轉。

圖片

方案:外層聚焦-設置tabindex=1,播報整塊內容設置aria-label拼接參數或aria-labelledby設置id,內層部分內容聚焦,聚焦內容設置tabindex=1,不聚焦的部分設置aria-hidden="true"(不然在選中外層焦點時候,內層非聚焦部分會重複播報)。


<div class="content" tabindex="1" aria-labelledby="title amount type rule btn">
  <div class="left" aria-hidden="true">
    <div id="amount" class="amount">
      <span class="num">{{ num }}</span>
      <span class="unit">元</span>
    </div>
    <div id="type" class="type">{{ type }}</div>
  </div>
  <div class="right">
    <div class="desc" aria-hidden="true">
      <div id="title" class="title">{{ title }}</div>
      <div id="rule" class="rule">{{ rule }}</div>
    </div>
    <div id="btn" tabindex="1" role="button" class="btn">{{ btn }}</div>
  </div>
</div>

11. 解決組件設置aria-labelledby="[id]”後只重複播報第一條數據的內容

因組件被Vue中的模板for循環調用,每個內容不一樣,用同一個id會導致播報同一個內容,且沒有key值的區分,需要解決設置aria-labelledby="[id]”後只重複播報第一條數據的內容

此時需要根據唯一標識區分id:可在id後拼接唯一標識號,如:"amount-${[item.id](http://item.id)}"

案例:

<div class="content" tabindex="1" :aria-labelledby="`amount-${item.id} desc-${item.id}`">
  <div id="amount" class="amount">{{ item.amount }}</div>
  <div id="desc" class="desc">{{ item.desc }}</div>
</div>

4.2 組件適配案例

除了具體業務的適配,還有一些共性的組件問題需要組件庫統一適配,這樣能減少各業務單獨適配的工作量。

任何一個無障礙組件的適配,都包含播報內容管理、焦點管理兩部分。對於播報內容管理,幾乎所有的組件適配都會涉及到。無障礙aria role、states一般系統都有自己默認的播報行為,儘量保持系統默認的播報。當然,也可以通過aria-label定製播報內容。焦點管理主要針對元素會發生變化的組件,如彈窗、輪播圖、各類選擇器等。

焦點管理的基本方法有3種:

  1. tabindex屬性;
  2. aria-hidden屬性;
  3. el.focus()方法。

tabindex="undefined"即意味着元素不可聚焦,為其他值意味着元素可聚焦。aria-hidden為true,意味着不可聚焦且不播報。對於可聚焦的元素,可以通過el.focus()方法直接聚焦在該元素上。

下面通過典型案例來説明各個組件是如何處理焦點和播報內容的。另外,由於一個html中可能多次使用同一個組件,所以下面的案例都是在不使用id屬性的基礎上完成適配的。如果一定要在某個組件使用id屬性,記得通過隨機函數對id屬性做隨機命名。

1. switch、checkbox、radio組件

這幾個組件相對簡單,使用系統默認的role即可。完成播報內容的適配即可,不需要做焦點管理。

以switch為例:首先是role=switch,然後通過aria-check播報開關狀態,最後通過aria-disabled來播報是否禁用。

<div
  class="nx-switch"
  tabindex="-1"
  :aria-checked="!!value"
  :aria-disabled="disabled"
  @click="onClick"
  role="switch"
>
  <div :style="barStyle"></div>
  <div :style="ringStyle"></div>
</div>

2.彈窗、對話框組件

彈窗組件是一個相對複雜的組件,既涉及到焦點管理,也涉及到播報內容管理。通過彈窗組件主要介紹焦點管理的小技巧。

該組件正在業務中的使用情況:

我們的彈窗組件相對比較通用,主要包括標題、內容、按鈕三個部分。其中,標題、內容既可以通過屬性傳入,也可以通過slot傳入。對於slot傳入的部分,組件不太好控制。在業務使用過程中,還普遍存在只有內容的彈窗。

適配目標:彈窗彈出時需要自動聚焦在標題上並播報。

彈窗焦點順序:彈窗彈出時需要自動聚焦在標題上並朗讀標題,然後手動移動下個焦點聚焦到説明文本,最後聚焦到操作按鈕。

圖片

(1)焦點管理

首先,需要解決彈窗彈出時的焦點聚焦問題。由於彈窗組件非常靈活,所以使用場景也非常多樣。對於屬性傳入的標題,組件內部可以獲取到該元素,並完成聚焦播報;對於slot插入的就顯得無能為力。

對於slot這種情況,組件和業務約定了一個屬性,如果業務想聚焦在這個dom元素上,就給該元素添加這個屬性。組件會通過el.querySelector查詢彈窗的後代元素,在彈窗彈出時自動添加tabindex並完成聚焦播報。

<nx-popup v-model="isPopupShow.center">
  <div
    nx-popup-aria-auto-focus="true"
    aria-label="無障礙播報測試"
    class="nx-popup-center"
  >
    Popup Center
  </div>
</nx-popup>

另外,考慮到有業務要求彈出時不自動聚焦在彈窗上,所以還提供了屬性,用於關閉焦點管理功能。

然後,需要解決彈窗關閉時的焦點還原問題。在彈窗彈出前保存當前聚焦的元素document.activeElement,關閉彈窗以後,通過el.focus()手動聚焦在該元素上。

還剩一個焦點穿透問題。很多安卓系統aria-modal屬性不起作用,所以焦點還能夠穿透彈窗,選中頁面上的元素。

目前組件沒有特別好的辦法處理這點。開發過程中,可以對需要屏蔽的元素添加aria-hidden=true屬性

(2)播報內容管理

這部分就顯得比較簡單。對於slot的情況,播報內容可以交給業務開發控制;對於屬性傳入的情況,可以添加組件屬性,為業務提供定製化播報的能力。

3.地址選擇器組件

地址選擇器是一個滾動式交互的組件。在這個組件的開發過程中使用到了一種小技巧,即不聚焦在某元素上,也能自動播報該元素變化的內容。

該組件在業務中的使用情況:業務開發沒有對該組件定製化,所以只用考慮組件內部的適配即可。

適配目標:在彈窗彈出時自動聚焦到標題上並播報。下圖是焦點的排布。

圖片

以該圖為例,焦點聚焦在第一列的時候,播報“河北省,滑動滾輪控件,可上下滾動切換”;另外,在每一列滾動結束時,播報當前選中的地址,如“河北省,石家莊市,橋西區”

(1)焦點管理

同彈窗組件。

(2)播報內容管理

這裏的難點就在於每一列滾動結束的時候,需要播報變化後的地址,但是此時焦點還在選中的這列上。

這裏添加了一個隱藏的元素(不在頁面上顯示),並添加屬性aria-live="polite",滾動結束的時候修改ariaPickerContent的值。如果不想播報則將ariaPickerContent置為空字符串,彈窗關閉的時候記得置為空。

<div class="nx-picker-scroll-aria" aria-live="polite">
  {{ ariaPickerContent }}
</div>

五、總結

本文是在vivo體系下的無障礙適配實踐,主要提煉總結了一些適配方法,對內外部的無障礙適配工作都有一定的參考和借鑑價值。下一步計劃豐富和完善組件庫的適配,沉澱一套高效的適配方案,儘量減少開發人員的適配成本,提高開發效率。

開發者應秉持着技術有温、代碼有愛的態度,堅守“勿以善小而不為”的準則,以用户為導向,讓我們的產品和服務照亮每一位用户,讓“障礙羣體”在這個有愛的互聯網時代,緊跟時代潮流。

user avatar shadowck 頭像 ximinghui 頭像
2 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.