前端優秀實踐不完全指南
阿新 • • 發佈:2021-02-24
本文其實應該叫,Web 使用者體驗設計提升指南。
一個 Web 頁面,一個 APP,想讓別人用的爽,也就是所謂的良好的使用者體驗,我覺得他可能包括但不限於:
+ 急速的開啟速度
+ 眼前一亮的 UI 設計
+ 酷炫的動畫效果
+ 豐富的個性化設定
+ 便捷的操作
+ 貼心的細節
+ 關注殘障人士,良好的可訪問性
+ ...
所謂的使用者體驗設計,其實是一個比較虛的概念,是秉承著**以使用者為中心的思想**的一種設計手段,以使用者需求為目標而進行的設計。設計過程注重以使用者為中心,使用者體驗的概念從開發的最早期就開始進入整個流程,並貫穿始終。
良好的使用者體驗設計,是產品每一個環節共同努力的結果。
除去一些很難一蹴而就的,本文將就**頁面展示**、**互動細節**、**可訪問性**三個方面入手,羅列一些在實際的開發過程中,積攢的一些有益的經驗。通過本文,你將能收穫到:
1. 瞭解到一些小細節是如何影響使用者體驗的
2. 瞭解到如何在儘量小的開發改動下,提升頁面的使用者體驗
3. 瞭解到一些優秀的互動設計細節
4. 瞭解基本的無障礙功能及頁面可訪問性的含義
5. 瞭解基本的提升頁面可訪問性的方法
## 頁面展示
就整個頁面的展示,頁面內容的呈現而言,有一些小細節是需要我們注意的。
### 整體佈局
先來看看一些佈局相關的問題。
對於大部分 PC 端的專案,我們首先需要考慮的肯定是最外層的一層包裹。假設就是 `.g-app-wrapper`。
```HTML
```
首先,對於 `.g-app-wrapper`,有幾點,是我們在專案開發前必須弄清楚的:
1. 專案是全屏佈局還是定寬佈局?
2. 對於全屏佈局,需要適配的最小的寬度是多少?
對於定寬佈局,就比較方便了,假設定寬為 `1200px`,那麼:
```CSS
.g-app-wrapper {
width: 1200px;
margin: 0 auto;
}
```
利用 `margin: 0 auto` 實現佈局的水平居中。在螢幕寬度大於 `1200px` 時,兩側留白,當然螢幕寬度小於 `1200px` 時,則出現滾動條,保證內部內容不亂。
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3ca2418b6ef046d7b2635a2e10a4610c~tplv-k3u1fbpfcp-watermark.image)
對於現代佈局,更多的是全屏佈局。其實現在也更提倡這種佈局,即使用可隨使用者裝置的尺寸和能力而變化的自適應佈局。
通常而言是左右兩欄,左側定寬,右側自適應剩餘寬度,當然,會有一個最小的寬度。那麼,它的佈局應該是這樣:
```HTML
```
```CSS
.g-app-wrapper {
display: flex;
min-width: 1200px;
}
.g-sidebar {
flex-basis: 250px;
margin-right: 10px;
}
.g-main {
flex-grow: 1;
}
```
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/865f09c168c04c808104b8540277a6f7~tplv-k3u1fbpfcp-watermark.image)
利用了 flex 佈局下的 `flex-grow: 1`,讓 `.main` 進行伸縮,佔滿剩餘空間,利用 `min-width` 保證了整個容器的最小寬度。
當然,這是最基本的自適應佈局。對於現代佈局,我們應該儘可能的考慮更多的場景。做到:
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4a1120cd519248cb8f706c63d28176be~tplv-k3u1fbpfcp-watermark.image)
### 底部 footer
下面一種情形也是非常常見的一個情景。
頁面存在一個 footer 頁尾部分,如果整個頁面的內容高度小於視窗的高度,則 footer 固定在視窗底部,如果整個頁面的內容高度大於視窗的高度,則 footer 正常流排布(也就是需要滾動到底部才能看到 footer)。
看看效果:
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4fe995f026044f63af807b601378294f~tplv-k3u1fbpfcp-watermark.image)
嗯,這個需求如果能夠使用 flex 的話,使用 `justify-content: space-between` 可以很好的解決,同理使用 `margin-top: auto` 也非常容易完成:
```HTML
...
```
```CSS
.g-container {
height: 100vh;
display: flex;
flex-direction: column;
}
.g-footer {
margin-top: auto;
flex-shrink: 0;
height: 30px;
background: deeppink;
}
```
[Codepen Demo -- sticky footer by flex margin auto](https://codepen.io/Chokcoco/pen/pmrbWX)
> 當然,實現它的方法有很多,這裡僅給出一種推薦的解法。
### 處理動態內容 - 文字超長
對於所有接收後端介面欄位的文字展示類的介面。都需要考慮全面(防禦性程式設計:所有的外部資料都是不可信的),正常情況如下,是沒有問題的。
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d465e773d8d84906ab73cd92d7e1e522~tplv-k3u1fbpfcp-zoom-1.image)
但是我們是否考慮到了文字會超長?超長了會折行還是換行?
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb60f1b3201140dfbe209ce6314ddb21~tplv-k3u1fbpfcp-zoom-1.image)
對於**單行文字**,使用單行省略:
```CSS
{
width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
```
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb95d86d3c664799a70f27de9b73f725~tplv-k3u1fbpfcp-zoom-1.image)
當然,目前對於**多行文字**的超長省略,相容性也已經非常好了:
```CSS
{
width: 200px;
overflow : hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
```
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6c5697f5f04440af94a2a584d7259eec~tplv-k3u1fbpfcp-zoom-1.image)
### 處理動態內容 - 保護邊界
對於一些動態內容,我們經常使用 `min/max-width` 或 `min/max-height` 對容器的高寬限度進行合理的控制。
在使用它們的時候,也有一些細節需要考慮到。
譬如經常會使用 `min-width` 控制按鈕的最小寬度:
```CSS
.btn {
...
min-width: 120px;
}
```
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/105a4921dba74801943595494321ba58~tplv-k3u1fbpfcp-zoom-1.image)
當內容比較少的時候是沒問題的,但是當內容比較長,就容易出現問題。使用了 `min-width` 卻沒考慮到按鈕的過長的情況:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ebec14ad3ed144a89faa33a456349f74~tplv-k3u1fbpfcp-zoom-1.image)
這裡就需要配合 padding 一起:
```CSS
.btn {
...
min-width: 88px;
padding: 0 16px
}
```
借用[Min and Max Width/Height in CSS](https://ishadeed.com/article/min-max-css/)中一張非常好的圖,作為釋義:
![min-width-2](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4bb7075934fe41fcaf08145a00ff7e34~tplv-k3u1fbpfcp-zoom-1.image)
### 0 內容展示
這個也是一個常常被忽略的地方。
頁面經常會有列表搜尋,列表展示。那麼,既然存在有資料的正常情況,當然也會存在搜尋不到結果或者列表無內容可展示的情形。
對於這種情況,一定要注意 0 結果頁面的設計,同時也要知道,這也是引導使用者的好地方。對於 0 結果頁面,分清楚:
+ **資料為空**:其中又可能包括了使用者無許可權、搜尋無結果、篩選無結果、頁面無資料
+ **異常狀態**:其中又可能包括了網路異常、伺服器異常、載入失敗等待
不同的情況可能對應不同的 0 結果頁面,附帶不同的操作引導。
譬如網路異常:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/110c59469b8d460fbbf5fc4f3d512c78~tplv-k3u1fbpfcp-zoom-1.image)
或者確實是 0 結果:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0985420f58b643ed9450deef3c7b6b87~tplv-k3u1fbpfcp-zoom-1.image)
關於 0 結果頁面設計,可以詳細看看這篇文章:[如何設計產品的空白頁面?](http://www.woshipm.com/pd/3742114.html)
小小總結一下,上述比較長的篇幅一直都在闡述一個道理,**開發時,不能僅僅關注正常現象,要多考慮各種異常情況,思考全面。做好各種可能情況的處理**。
### 圖片相關
圖片在我們的業務中應該是非常的常見了。有一些小細節是需要注意的。
#### 給圖片同時設定高寬
有的時候和產品、設計會商定,只能使用固定尺寸大小的圖片,我們的佈局可能是這樣:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f963c109bf47406bb21b9a8e4e70e1af~tplv-k3u1fbpfcp-zoom-1.image)
對應的佈局:
```HTML
First section
Second section
Third section
```
不使用 `scroll-behavior: smooth`,是突兀的跳動切換:
![scrol](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ece4fb09a046457f9b59ec8dc9e82f86~tplv-k3u1fbpfcp-zoom-1.image)
給可滾動容器新增 `scroll-behavior: smooth`,實現平滑滾動:
```CSS
{
scroll-behavior: smooth;
}
```
![scroll2](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/efcabe865fa44d07b89d48b6ba4d4c40~tplv-k3u1fbpfcp-zoom-1.image)
#### 使用 `scroll-snap-type` 優化滾動效果
`sroll-snap-type` 可能算得上是新的滾動規範裡面最核心的一個屬性樣式。
**[scroll-snap-type](https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-snap-type)**:屬性定義在滾動容器中的一個臨時點(snap point)如何被嚴格的執行。
光看定義有點難理解,簡單而言,這個屬性規定了一個容器是否對內部滾動動作進行捕捉,並且規定了如何去處理滾動結束狀態。讓滾動操作結束後,元素停止在適合的位置。
看個簡單示例:
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fdc2b27afaef4b5782842a74cbe7cdef~tplv-k3u1fbpfcp-zoom-1.image)
當然,`scroll-snap-type` 用法非常多,可控制優化的點很多,限於篇幅無法一一展開,具體更詳細的用法可以看看我的另外一篇文章 -- [使用 sroll-snap-type 優化滾動](https://github.com/chokcoco/iCSS/issues/74)
#### 控制滾動層級,避免頁面大量重排
這個優化可能稍微有一點難理解。需要了解 CSS 渲染優化的相關知識。
先說結論,控制滾動層級的意思是**儘量讓需要進行 CSS 動畫(可以是元素的動畫,也可以是容器的滾動)的元素的 z-index 保持在頁面最上方,避免瀏覽器建立不必要的圖形層(GraphicsLayer),能夠很好的提升渲染效能**。
這一點怎麼理解呢,一個元素觸發建立一個 Graphics Layer 層的其中一個因素是:
+ 元素有一個 z-index 較低且包含一個複合層的兄弟元素
根據上述這點,我們對滾動效能進行優化的時候,需要注意兩點:
1. 通過生成獨立的 GraphicsLayer,利用 GPU 加速,提升滾動的效能
2. 如果本身滾動沒有效能問題,不需要獨立的 GraphicsLayer,也要注意滾動容器的層級,避免因為層級過高而被其他建立了 GraphicsLayer 的元素合併,被動的生成一個 Graphics Layer ,影響頁面整體的渲染效能
如果你對這點還有點懵,可以看看這篇文章 -- [你所不知道的 CSS 動畫技巧與細節](https://github.com/chokcoco/iCSS/issues/27)
### 點選互動優化
在使用者點選互動方面,也有一些有意思的小細節。
#### 優化手勢 -- 不同場景應用不同 `cursor`
對於不同的內容,最好給與不同的 `cursor` 樣式,CSS 原生提供非常多種常用的手勢。
在不同的場景使用不同的滑鼠手勢,**符合使用者的習慣與預期**,可以很好的提升使用者的互動體驗。
首先對於按鈕,就至少會有 3 種不同的 cursor,分別是可點選,不可點選,等待中:
```CSS
{
cursor: pointer; // 可點選
cursor: not-allowed; // 不可點選
cursor: wait; // loading
}
```
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a92c0971be934ba9a7555b973ac27a5b~tplv-k3u1fbpfcp-zoom-1.image)
除此之外,還有一些常見的,對於一些可輸入的 Input 框,使用 `cursor: text`,對於提示 Tips 類使用 `cursor: help`,放大縮小圖片 `zoom-in`、`zoom-out` 等等:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4b0c2e854e243d9bd07c7d82b71d57f~tplv-k3u1fbpfcp-zoom-1.image)
一些常用的簡單列一列:
+ 按鈕可點選: `cursor: pointer`
+ 按鈕禁止點選:`cursor: not-allowed`
+ 等待 Loading 狀態:`cursor: wait`
+ 輸入框:cursor: text;
+ 圖片檢視器可放大可縮小:`cursor: zoom-in/ zoom-out`
+ 提示:cursor: help;
當然,實際 `cursor` 還支援非常多種,可以在 [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) 或者下面這個 CodePen Demo 中檢視這裡看完整的列表:
[CodePen Demo -- Cursor Demo](https://codepen.io/Chokcoco/pen/poEBLqr)
### 點選區域優化 -- 偽元素擴大點選區域
按鈕是我們網頁設計中十分重要的一環,而按鈕的設計也與使用者體驗息息相關。
考慮這樣一個場景,在搖晃的車廂上或者是單手操作著螢幕,有的時候一個按鈕,死活也點不到。
讓使用者更容易的點選到按鈕無疑能很好的增加使用者體驗及可提升頁面的訪問性,尤其是在移動端,按鈕通常都很小,但是受限於設計稿或者整體 UI 風格,我們不能直接去改變按鈕元素的高寬。
那麼這個時候有什麼辦法在不改變按鈕原本大小的情況下去增加他的點選熱區呢?
這裡,偽元素也是可以代表其宿主元素來響應的滑鼠互動事件的。藉助偽元素可以輕鬆幫我們實現,我們可以這樣寫:
```CSS
.btn::befoer{
content:"";
position:absolute;
top:-10px;
right:-10px;
bottom:-10px;
left:-10px;
}
```
當然,在 PC 端下這樣子看起來有點奇怪,但是合理的用在點選區域較小的移動端則能取到十分好的效果,效果如下:
![608782-20160527112625428-906375003](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/499f2bb64ead4a1fb36639b66fe98649~tplv-k3u1fbpfcp-zoom-1.image)
在按鈕的偽元素沒有其它用途的時候,這個方法確實是個很好的提升使用者體驗的點。
### 快速選擇優化 -- `user-select: all`
作業系統或者瀏覽器通常會提供一些快速選取文字的功能,看看下面的示意圖:
![layout3](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79cb00558fe14e50bafb20b2b4b47e9e~tplv-k3u1fbpfcp-zoom-1.image)
快速單擊兩次,可以選中單個單詞,快速單擊三次,可以選中一整行內容。但是如果有的時候我們的核心內容,被分隔符分割,或者潛藏在一整行中的一部分,這個時候選取起來就比較麻煩。
利用 `user-select: all`,可以將需要一次選中的內容進行包裹,使用者只需要點選一次,就可以選中該段資訊:
```CSS
.g-select-all {
user-select: all
}
```
給需要一次選中的資訊,加上這個樣式後的效果,這個細節作用在一些需要複製貼上的場景,非常好用:
![layout4](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a18f99c8e4064164815ecc5c6e774994~tplv-k3u1fbpfcp-zoom-1.image)
[CodePen -- user-select: all 示例](https://codepen.io/Chokcoco/pen/LYRBmEJ)
### 選中樣式優化 -- `::selection`
當然,如果你想更進一步,CSS 還有提供一個 `::selection` 偽類,可以控制選中的文字的樣式(只能控制`color`, `background`, `text-shadow`),進一步加深效果。
![layout5](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cbbfee432f0042c794109cea39403faf~tplv-k3u1fbpfcp-zoom-1.image)
[CodePen -- user-select: all && ::selection 控制選中樣式](https://codepen.io/Chokcoco/pen/RwGYWNj)
### 新增禁止選擇 -- `user-select: none`
有快速選擇,也就會有它的對立面 -- 禁止選擇。
對於一些可能頻繁操作的按鈕,可能出現如下尷尬的場景:
+ 文字按鈕的快速點選,觸發了瀏覽器的雙擊快速選擇,導致文字被選中:
![btn-click](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dac6a94c134b48d4a41cb806ef1f61d4~tplv-k3u1fbpfcp-zoom-1.image)
+ 翻頁按鈕的快速點選,觸發了瀏覽器的雙擊快速選擇:
[](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da77c284cd0f4aafb183a5667c5cf32c~tplv-k3u1fbpfcp-watermark.image)
對於這種場景,我們需要把不可被選中元素設定為不可被選中,利用 CSS 可以快速的實現這一點:
```CSS
{
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
```
這樣,無論點選的頻率多快,都不會出現尷尬的內容選中:
![btn-click-unselect](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/db6252ae280b4a07902fa9925854d842~tplv-k3u1fbpfcp-zoom-1.image)
### 跳轉優化
現階段,單頁應用(Single Page Application)的應用非常廣泛,Vue 、React 等框架大行其道。但是一些常見的寫法,也容易衍生一些小問題。
譬如,點選按鈕、文字進行路由跳轉。譬如,經常會出現這種程式碼:
```Javascirpt
...
...
...
gotoDetail() {
this.$router.push({
name: 'xxxxx',
});
}
```
大致邏輯就是給按鈕新增一個事件,點選之後,跳轉到另外一個路由。當然,本身這個功能是沒有任何問題的,但是沒有考慮到使用者實際使用的場景。
實際使用的時候,由於是一個頁面跳轉,很多時候,使用者希望能夠保留當前頁面的內容,同時開啟一個新的視窗,這個時候,他會嘗試下的滑鼠右鍵,選擇**在新標籤頁中開啟頁面**,遺憾的是,上述的寫法是不支援滑鼠右鍵開啟新頁面的。
原因在於瀏覽器是通過讀取 `` 標籤的 `href` 屬性,來展示類似**在新標籤頁中開啟頁面**這種選項,對於上述的寫法,瀏覽器是無法識別它是一個可以跳轉的連結。簡單的示意圖如下:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/014661e440d04592acc1faf0c7280711~tplv-k3u1fbpfcp-zoom-1.image)
所以,對於所有路由跳轉按鈕,建議都使用 `` 標籤,並且內建 `href` 屬性,填寫跳轉的路由地址。實際渲染出來的 DOM 可能是需要類似這樣:
```HTML
Detail
```
### 易用性
易用性也是互動設計中需要考慮的一個非常重要的環節,能做的有非常多。簡單的羅列一下:
+ 注意介面元素的一致性,降低使用者學習成本
+ 延續使用者日常的使用習慣,而不是重新創造
+ 給下拉框增加一些預設值,降低使用者填寫成本
+ 同類的操作合併在一起,降低使用者的認知成本
+ 任何操作之後都要給出反饋,讓使用者知道操作已經生效
### 先探索,後表態
這一點非常的有意思,什麼叫先探索後表態呢?就是我們不要一上來就強迫使用者去做一些事情,譬如登入。
想一想一些常用網站的例子:
+ 類似虎牙、Bilibili 等視訊網站,可以先觀看體驗,一定觀看時間後才會要求登入(登入享受藍光)
+ 電商網站,只有到付款的時候,才需要登入
上述**易用性**和**先探索,後表態**的內容,部分來源於:[Learn From What Leading Companies A/B Test](https://goodui.org/),可以好好讀一讀。
## 字型優化
字型的選擇與使用其實是非常有講究的。
如果網站沒有強制必須使用某些字型。最新的規範建議我們更多的去使用系統預設字型。也就是 [CSS Fonts Module Level 4 -- Generic font families](https://www.w3.org/TR/css-fonts-4/#generic-font-families) 中新增的 `font-family: system-ui` 關鍵字。
`font-family: system-ui` 能夠自動選擇本作業系統下的預設系統字型。
預設使用特定作業系統的系統字型可以提高效能,因為瀏覽器或者 webview 不必去下載任何字型檔案,而是使用已有的字型檔案。 `font-family: system-ui` 字型設定的優勢之處在於它與當前作業系統使用的字型相匹配,對於文字內容而言,它可以得到最恰當的展示。
舉兩個例子,天貓的字型定義與 Github 的字型定義:
+ [天貓](https://www.tmall.com/):`font-family: "PingFang SC",miui,system-ui,-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,sans-serif;`
+ [Github](https://github.com):`font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;`
簡單而言,它們總體遵循了這樣一個基本原則:
#### 1、儘量使用系統預設字型
使用系統預設字型的主要原因是效能,並且系統字型的優點在於它與當前作業系統使用的相匹配,因此它的文字展示必然也是一個讓人舒適展示效果。
#### 2、兼顧中西,西文在前,中文在後
中文或者西文(英文)都要考慮到。由於大部分中文字型也是帶有英文部分的,但是英文部分又不怎麼好看,但是英文字型中大多不包含中文。通常會先進行英文字型的宣告,選擇最優的英文字型,這樣不會影響到中文字型的選擇,中文字型宣告則緊隨其次。
#### 3、兼顧多作業系統
選擇字型的時候要考慮多作業系統。例如 MAC OS 下的很多中文字型在 Windows 都沒有預裝,為了保證 MAC 使用者的體驗,在定義中文字型的時候,先定義 MAC 使用者的中文字型,再定義 Windows 使用者的中文字型;
#### 4、兼顧舊作業系統,以字型族系列 serif 和 sans-serif 結尾
當使用一些非常新的字型時,要考慮向下相容,兼顧到一些極舊的作業系統,使用字型族系列 serif 和 sans-serif 結尾總歸是不錯的選擇。
對於上述的一些字型可能會有些懵,譬如 `-apple-system`, `BlinkMacSystemFont`,這是因為不同瀏覽器廠商對規範的實現有所不同,對於字型定義更多的相關細節,可以再看看這篇文章 -- [Web 字型 font-family 再探祕](https://github.com/chokcoco/iCSS/issues/69)
## 可訪問性(A11Y)
可訪問性,在我們的網站中,屬於非常重要的一環,但是大部分前端(其實應該是設計、前端、產品)同學都會忽視它。
> 我潛伏在一個叫**無障礙設計小組**的群裡,其中包含了很多無障礙設計師以及患有一定程度視覺、聽力、行動障礙的使用者,他們在群裡經常會表達出一個觀點,就是國內的大部分 Web 網站及 APP 基本沒有考慮過殘障人士的使用(或者可訪問性做的非常差),非常的令人揪心。
尤其在我們一些重互動、重邏輯的網站中,我們需要考慮使用者的使用習慣、使用場景,從高可訪問性的角度考慮,譬如假設使用者沒有滑鼠,僅僅使用鍵盤,能否順暢的使用我們的網站?
> 假設使用者沒有滑鼠,這個真不一定是針對殘障人士,很多情況下,使用者拿滑鼠的手可能在幹其他事情,比如在吃東西,又或者在 TO B 類的業務,如超市收銀、倉庫收貨,很可能使用者拿滑鼠的手操作著其他裝置(掃碼槍)等等。
本文不會專門闡述無障礙設計的方方面面,只是從一些我覺得前端工程師需要關注的,並且僅需要花費少量代價就能做好的一些無障礙設計細節。記住,**無障礙設計對所有人都更友善**。
### 色彩對比度
顏色,也是我們天天需要打交道的屬性。對於大部分視覺正常的使用者,可能對頁面的顏色敏感度還沒那麼高。但是對於一小部分色弱、色盲使用者,他們對於網站的顏色會更加敏感,不好的設計會給他們訪問網站帶來極大的不便。
#### 什麼是色彩對比度
是否曾關心過頁面內容的展示,使用的顏色是否恰當?色弱、色盲使用者能否正常看清內容?良好的色彩使用,在任何時候都是有益的,而且不僅僅侷限於對於色弱、色盲使用者。在戶外用手機、陽光很強看不清,符合無障礙標準的高清晰度、高對比度文字就更容易閱讀。
這裡就有一個概念 -- **顏色對比度**,簡單地說,描述就是兩種顏色在亮度(Brightness)上的差別。運用到我們的頁面上,大多數的情況就是背景色(background-color)與內容顏色(color)的對比差異。
最權威的網際網路無障礙規範 —— [WCAG AA](https://www.w3.org/Translations/WCAG21-zh/)規範規定,所有重要內容的色彩對比度需要達到 4.5:1 或以上(字號大於18號時達到 3:1 或以上),才算擁有較好的可讀性。
借用一張圖 -- [知乎 -- 助你輕鬆做好無障礙的15個UI設計工具推薦](https://zhuanlan.zhihu.com/p/349761993):
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f86c3e1740e047df9848c93a9d02f868~tplv-k3u1fbpfcp-zoom-1.image)
很明顯,上述最後一個例子,文字已經非常的不清晰了,正常使用者都已經很難看得清了。
#### 檢查色彩對比度的工具
Chrome 瀏覽器從很早開始,就已經支援檢查元素的色彩對比度了。以我當前正在寫作的頁面為例子,`Github Issues` 編輯頁面的兩個按鈕:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/db126d25715344aebeebb36f458a117d~tplv-k3u1fbpfcp-zoom-1.image)
審查元素,分別可以看到兩個按鈕的色彩對比度:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b20e7bfa0f9c466b8ab3df0b9e6008f0~tplv-k3u1fbpfcp-zoom-1.image)
可以看到,綠底白字按鈕的色彩對比度是沒有達到標準的,也被用黃色的歎號標識了出來。
除此之外,在審查元素的 Style 介面的取色器,改變顏色,也能直觀的看到當前的色彩對比度:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a7b1f9ed4524c4796a5d14ed88c1b26~tplv-k3u1fbpfcp-zoom-1.image)
### 焦點響應
類似百度、谷歌的首頁,進入頁面後會預設讓輸入框獲得焦點:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8a7754968564e0fa157ed2f0dae7207~tplv-k3u1fbpfcp-zoom-1.image)
並非所有的有輸入框的頁面,都需要進入頁面後進行聚焦,但是焦點能夠讓使用者非常明確的知道,當前自己在哪,需要做些什麼。尤其是對於無法操作滑鼠的使用者。
頁面上可以聚焦的元素,稱為**可聚焦元素**,獲得焦點的元素,則會觸發該元素的 `focus` 事件,對應的,也就會觸發該元素的 `:focus` 偽類。
瀏覽器通常會使用元素的 `:focus` 偽類,給元素新增一層邊框,告訴使用者,當前的獲焦元素在哪裡。
我們可以通過鍵盤的 `Tab` 鍵,進行焦點的切換,而獲焦元素則可以通過元素的 `:focus` 偽類的樣式,告訴使用者當前焦點位置。
> 當然,除了 `Tab` 鍵之外,對於一些多輸入框、選擇框的表單頁面,我們也應該想著如何簡化使用者的操作,譬如使用者按回車鍵時自動前進到下一欄位。一般而言,使用者必須執行的觸按越少,體驗越佳。:thumbsup:
**下面的截圖,完全由鍵盤操作完成**:
![a11y](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/71861c823f874294bb4bd76500652b84~tplv-k3u1fbpfcp-zoom-1.image)
通過元素的 `:focus` 偽類以及鍵盤 Tab 鍵切換焦點,使用者可以非常順暢的在脫離滑鼠的情況下,對頁面的焦點切換及操作。
然而,在許多 `reset.css` 中,經常能看到這樣一句 CSS 樣式程式碼,為了樣式的統一,消除了可聚焦元素的 `:focus` 偽類:
```CSS
:focus {
outline: 0;
}
```
我們給上述操作的程式碼。也加上這樣一句程式碼,**全程再用鍵盤操作一下**:
![a11y2](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4266363a7d454e13aac72610e44d07ec~tplv-k3u1fbpfcp-zoom-1.image)
除了在 `input` 框有游標提示,當使用 Tab 進行焦點切換到 `select` 或者到 `button` 時,由於沒有了 `:focus` 樣式,使用者將完全懵逼,不知道頁面的焦點現在處於何處。
#### 保證非滑鼠使用者體驗,合理運用 `:focus-visible`
當然,造成上述結果很重要的一個原因在於。`:focus` 偽類不論使用者在使用滑鼠還是使用鍵盤,只要元素獲焦,就會觸發。
而其本身的預設樣式又不太能被產品或者設計接受,導致了很多人會在焦點元素觸發 `:focus` 偽類時,通過改變 border 的顏色或者其他一些方式替代或者直接禁用。而這樣做,從可訪問性的角度來看,對於非滑鼠使用者,無疑是災難性的。
基於此,在[W3 CSS selectors-4 規範](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo) 中,新增了一個非常有意思的 `:focus-visible` 偽類。
`:focus-visible`:這個選擇器可以有效地根據使用者的輸入方式(滑鼠 vs 鍵盤)展示不同形式的焦點。
有了這個偽類,就可以做到,當用戶使用滑鼠操作可聚焦元素時,不展示 `:focus` 樣式或者讓其表現較弱,而當用戶使用鍵盤操作焦點時,利用 `:focus-visible`,讓可獲焦元素獲得一個較強的表現樣式。
看個簡單的 Demo:
```HTML
```
```CSS
button:active {
background: #eee;
}
button:focus {
outline: 2px solid red;
}
```
使用滑鼠點選:
![a11y3](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ca5170bb70934d729533f64b5e5db13d~tplv-k3u1fbpfcp-zoom-1.image)
可以看到,使用滑鼠點選的時候,觸發了元素的 `:active` 偽類,也觸發了 `:focus`偽類,不太美觀。但是如果設定了 `outline: none` 又會使鍵盤使用者的體驗非常糟糕。嘗試使用 `:focus-visible` 偽類改造一下:
```CSS
button:active {
background: #eee;
}
button:focus {
outline: 2px solid red;
}
button:focus:not(:focus-visible) {
outline: none;
}
```
看看效果,分別是在滑鼠點選 Button 和使用鍵盤控制焦點點選 Button:
![a11y4](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6a93f5cfc294a6d9316eda9c33762f7~tplv-k3u1fbpfcp-zoom-1.image)
[CodePen Demo -- :focus-visible example](https://codepen.io/Chokcoco/pen/abBbPrE)
可以看到,使用滑鼠點選,不會觸發 `:foucs`,只有當鍵盤操作聚焦元素,使用 Tab 切換焦點時,`outline: 2px solid red` 這段程式碼才會生效。
這樣,我們就既保證了正常使用者的點選體驗,也保證了一批無法使用滑鼠的使用者的焦點管理體驗。
值得注意的是,有同學會疑惑,這裡為什麼使用了 `:not` 這麼繞的寫法而不是直接這樣寫呢:
```CSS
button:focus {
outline: unset;
}
button:focus-visible {
outline: 2px solid red;
}
```
為的是相容不支援 `:focus-visible` 的瀏覽器,當 `:focus-visible` 不相容時,還是需要有 `:focus` 偽類的存在。
### 使用 WAI-ARIA 規範增強語義 -- div 等非可獲焦元素模擬獲焦元素
還有一個非常需要注意的點。
現在很多前端同學在前端開發的過程中,喜歡使用非可獲焦元素模擬獲焦元素,譬如:
+ 使用 `div` 模擬 `button` 元素
+ 使用 `ul` 模擬下拉列表 `select` 等等
當下很多元件庫都是這樣做的,譬如 element-ui 和 ant-design。
在使用非可獲焦元素模擬獲焦元素的時候,一定要注意,不僅僅只是外觀長得像就完事了,其行為表現也需要符合原本的 `button`、`select` 等可聚焦元素的性質,能夠體現元素的語義,能夠被聚焦,能夠通過 Tab 切換等等。
基於大量類似的場景,有了 [WAI-ARIA 標準](https://www.w3.org/TR/wai-aria-1.1/),WAI-ARIA是一個為殘疾人士等提供無障礙訪問動態、可互動Web內容的技術規範。
簡單來說,它提供了一些屬性,增強標籤的語義及行為:
+ 可以使用 `tabindex` 屬性控制元素是否可以聚焦,以及它是否/在何處參與順序鍵盤導航
+ 可以使用 `role` 屬性,來標識元素的語義及作用,譬如使用 `Save` 來模擬一個按鈕
+ 還有大量的 `aria-*` 屬性,表示元素的屬性或狀態,幫助我們進一步地識別以及實現元素的語義化,優化無障礙體驗
#### 使用工具檢視標籤的語義
我們來看看 Github 頁面是如何定義一個按鈕的,以 Github Issues 頁面的 Edit 按鈕為例子:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e9e7a8e882ee4fbdbedeb23b80a36dfd~tplv-k3u1fbpfcp-zoom-1.image)
這一塊,清晰的描述了這個按鈕在可訪問性相關的一些特性,譬如 Contrast 色彩對比度,按鈕的描述,也就是 `Name`,是給螢幕閱讀器看到的,`Role` 標識是這個元素的屬性,它是一個按鈕,`Keyboard focusable` 則表明他能否被鍵盤的 Tab 按鈕給捕獲。
### 分析使用非可聚焦元素模擬的按鈕
這裡,我隨便選取了我們業務中一個使用 span 模擬按鈕的場景,是一個麵包屑導航,點選可進行跳轉,發現慘不忍睹:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2fb99dc2cac14bd2956fa2b15282152a~tplv-k3u1fbpfcp-zoom-1.image)
HTML 程式碼:
```HTML
Inbound
```
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/481fd7dad34a4cb1a02b4d7a0a7e4763~tplv-k3u1fbpfcp-zoom-1.image)
基本上可訪問性為 0,作為一個按鈕,它不可被聚焦,無法被鍵盤使用者選中,沒有具體的語義,色彩對比度太低,可能視障使用者無法看清。並且,作為一個能進行頁面跳轉的按鈕,它沒有不是 `a` 標籤,沒有 `href` 屬性。
即便對於麵包屑導航,我們可以不將它改造成 `` 標籤,也需要做到**最基本**的一些可訪問性改造:
```HTML
Inbound
```
不要忘了再改一下顏色,達到最低色彩對比度以上,再看看:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e312c94dde642aaa2f2bf341a1a4bb4~tplv-k3u1fbpfcp-zoom-1.image)
OK,這樣,一個最最最基本的,滿足最低可訪問性需求的按鈕算是勉強達標,當然,這個按鈕可以再更進一步進行改造,涉及了更深入的可訪問性知識,本文不深入展開。
### 分析元件庫的 A11Y
最後,在我們比較常用的 Vue - [element-ui](https://element.eleme.io/#/zh-CN/component/select)、React - [ant-design](https://ant.design/components/select-cn/) 中,我們來看看 ant-design 在提升可訪問性相關的一些功能。
以 Select 選擇框元件為例,ant-design 利用了大量的 WAI-ARIA 屬性,使得用 div 模擬的下拉框不僅僅在表現上符合一個下拉框,在語義、行為上都符合一個下拉框,簡單的一個例子:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/323ff57ad325409cb02ae208550c5a6b~tplv-k3u1fbpfcp-zoom-1.image)
看看使用 div 模擬下拉框的 DOM 部分:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0603a19d8ba7452ba4a3ce30e741dc0c~tplv-k3u1fbpfcp-zoom-1.image)
再看看在互動體驗上:
![a11y5](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65fb74a9dc334bf4953a22e7a0855d0e~tplv-k3u1fbpfcp-zoom-1.image)
上述操作全是在鍵盤下完成,看著平平無奇,實際上元件庫在正常響應可獲焦元素切換的同時,給用 div 模擬的 select 加了很多鍵盤事件的響應,可以利用回車,上下鍵等對可選項進行選擇。其實是下了很多功夫。
對於 A11Y 相關的內容,篇幅及內容非常之多,本文無法一一展開,感興趣的可以通讀下下列文章:
+ [WAI-ARIA basics](https://developer.mozilla.org/zh-CN/docs/learn/Accessibility/WAI-ARIA_basics)
+ [WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria-1.1/)
+ [Web中的焦點管理](https://www.w3cplus.com/a11y/focus-for-web.html)
+ [無障礙功能](https://developers.google.com/web/fundamentals/accessibility?hl=zh-cn)
+ [提升Web使用者體驗的71個設計要點](https://www.shejidaren.com/71-good-ui-tips.html)
+ [公眾號 -- 無障礙設計小組](https://werss.app/accounts/NTI1NTUwNTg1ODU0NTM=)
## 總結一下
本文從**頁面展示**、**互動細節**、**可訪問性**三個大方面入手,羅列一些在實際的開發過程中,積攢的一些有益的經驗。雖然不夠全面,不過從一開始也就沒想著大而全,主要是一些可能有用但是容易被忽視的點,也算是一個不錯的查缺補漏小指南。
當然,很多都是我個人的觀點想法,可能有一些理解存在一些問題,一些概念沒有解讀到位,也希望大家幫忙指出。
## 最後
本文到此結束,希望對你有幫助 :)
更多精彩 CSS 技術文章彙總在我的 [Github -- iCSS](https://github.com/chokcoco/iCSS) ,持續更新,歡迎點個 star 訂閱收藏。
如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。
-
圖片描述