Accessibility 學習筆記(1) === ![](https://i.imgur.com/nwfFQyB.png) ###### tags: `accessibility`, `React`, `ally`, `無障礙設計` # 使用工具來偵測網頁無障礙以及確保每一個網頁都具備 h1 標籤 這篇主要紀錄自己在學習無障礙設計相關知識,此篇分享的方法來自於 Marcy Sutton 的 [Testing Accessibility](https://testingaccessibility.com/) 課程。 ## 幫助網頁進行無障礙設計檢測的擴充工具 Chrome 擴充裡面有幾個好用的工具,可以協助開發者進行無障礙相關的檢測,以下分享幾個常用的: 1. [Accessibility Insights for Web](https://chrome.google.com/webstore/detail/accessibility-insights-fo/pbjjkligggfmakdaogkfomddhfmpjeni) 2. [Web developer](https://chrispederick.com/work/web-developer/) 3. [WAVE Evaluation tool](https://chrome.google.com/webstore/detail/wave-evaluation-tool/jbbplnpkjmmeebjpijfedlgcdilocofh) ## heading 標籤的使用 一個網頁的 heading 要怎麼規劃,一般來說每一個網頁都須具備一個 `h1` 標籤,接著按照網頁的規劃從 `h2`、`h3` 到 `h6`,以下我們將透過 **Web developer** 工具來查看網頁的標題標籤的結構。 課程內提供的網頁如下: ![](https://i.imgur.com/UmfbmiS.jpg) ### 使用 Web DEveloper 工具: 接著我們按照這個步驟來檢視網頁的結構: 1. 到 Chrome 安裝 **Web developer** 擴充。 2. 安裝完畢後會出現在網頁的右上角,圖似一個小齒輪,左鍵點擊。 3. 選擇 **View Document Outline**。 ![](https://i.imgur.com/ddPXT0f.png) 檢視結果如下:發現我們缺少一個 `h1` 標籤。 ![](https://i.imgur.com/IBSH2DW.png) > `h1` 主要在做什麼的? > 對於螢幕閱讀器使用者來說,進入一個網頁內最需要瞭解的莫過於該網頁的主要目的,`h1` 主要提供給他們一個快速理解所在網頁的目的或者相關資訊。 ### 其他視覺上的小缺失 另外我們也可以觀察到網頁中有一些副標題類的,似乎太小,視覺上並沒有區分出層級以及距離。 ![](https://i.imgur.com/YSsR9fk.png) ## 該怎麼把 `h1` 放進去? 假設網頁由你規劃,在了解無障礙設計的原則之後,相信都會把標題標籤的使用規劃進去,但萬一是你要維護的舊網頁呢?幾個面向可以思考: 1. 把網頁再重新規劃。 2. 直接把 `h1` 加在導航欄下。 以上都可以做到,但是都會需要動用已經規劃好的結構,以及可能會需要和相關部門如 UI/UX 等溝通協調。 ## 解決辦法 透過 `React` 提供的 `Portal` 功能,[官網](https://reactjs.org/docs/portals.html#gatsby-focus-wrapper)是這樣描述這個功能的 `Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.` 我們先在 `index.html` 內建立一個 `<div id="portal-root"></div>` 並置於最上層,這樣當我們使用 `HeaderPortal` 組件時,所呈現的任何内容都將高於網頁中其他所有内容的層次結構。 ### portal-root(naming is not mandatory) ```htmlembedded! <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="initial-scale=1" /> <title>CampSpots</title> </head> <body> <div id="portal-root"></div> <div id="app-root"></div> <script type="module" src="index.js"></script> </body> </html> ``` ### creatPortal 透過建立一個 `HeaderPortal` 的組件,然後引用 `createPortal` 模組。 這個組件將會在網頁渲染後,抓取到 `id` 為 `portal-root` 的 `<div>`,然後建立另一個 `<div>` 並加入到 `<div id="portal-root"></div>` 之下。 ```javascript! import React, { useEffect } from "react" import { createPortal } from "react-dom" const HeaderPortal = ({children}) => { const mount = document.getElementById("portal-root") const el = document.createElement("div") useEffect(() => { mount.appendChild(el) return () => mount.removeChild(el) }, [el, mount]) return createPortal(children, el) } export default HeaderPortal ``` ```javascript! // 網頁組件 // 此省略其餘引用的組件和模組等 import HeaderPortal from './header-portal' const Listing = props => { const data = ListingsData.listings[props.id] const headerImageUrl = LoadedImageUrl(imageURLs, data.detailHeaderImageSrc) return ( <BodyClassName className="header-overlap page-listing-detail"> <> <div className="page-header" style={{ backgroundImage: `url(${headerImageUrl}` }} > <div className="page-header-content wide-layout"> <div className="listing-name">{data.listingName}</div> <p className="location">{data.location}</p> </div> </div> <div className="wide-layout two-parts-70-30"> <div> <div>Description</div> <div className="description-text" dangerouslySetInnerHTML={{ __html: sanitizeHtml(data.description) }} /> <div> Amenities </div> <div className="amenity-icons grid"> {data.amenities.map((amenity, index) => { return <div key={index}> <Icon name={amenity} showText={true} /> </div> })} </div> </div> <div> <div className="h4-style"> Calendar <DatePicker /> </div> </div> </div> <div className="wide-layout"> <div className="detail-images"> {data.detailImages.map((image, index) => { let detailImageUrl = LoadedImageUrl(imageURLs, image.imageSrc) return <img key={index} src={detailImageUrl} alt={image.imageAlt} /> })} </div> </div> </div> </> </BodyClassName> ) } export default Listing ``` 上面是尚未將 `HeaderPortal` 組件加入到該網頁的組件中的 DOM tree 結構,可以看到它只是單純的一個 `<div>`,如下圖: ![](https://i.imgur.com/LrOinsd.png) ```javascript! import HeaderPortal from './header-portal' const Listing = props => { const data = ListingsData.listings[props.id] const headerImageUrl = LoadedImageUrl(imageURLs, data.detailHeaderImageSrc) return ( <BodyClassName className="header-overlap page-listing-detail"> <> // 這邊加入 <HeaderPortal> 組件 <HeaderPortal> <h1>Camp Spots - {data.listingName}</h1> </HeaderPortal> ... </> ) } // 以下省略 ``` 下圖是把 `<HeaderPortal>` 加入到組件的頂層後,在 Devtools 內的 DOM tree 的結構,會看到我們順利把新的元素嵌入進去。 ![](https://i.imgur.com/IL7QBTn.png) 但是問題來了,把標題加進去後,毀了我們的版面,此時我們可以使用 `CSS` 來隱藏它,且也可以與行銷部門討論該怎麼描述這個 `h1` 標題,這樣即使隱藏起來,螢幕閱讀器使用者也依舊可以知道這個網頁的用途和相關資訊了,下圖是把 `h1` 標籤內文再寫得更詳細,透過螢幕閱讀器的樣子。 ![](https://i.imgur.com/PWuqdPx.png) 下圖是使用螢幕閱讀器的顯示,這樣視障人士就會知道說這個頁面是要預定旅程的。 ![](https://i.imgur.com/kQv7fiL.png) ## 整理其他標籤 經過後續的整理,就有結構比較完整的網頁了。 ![](https://i.imgur.com/FnT413S.png)