在 2017 年時曾經讀過一篇文章 – Effective前端1:能使用html/css解决的问题就不要使用JS,第一次閱讀時覺得深有同感,也學到很多當時還不熟的技巧,很推薦大家看看。雖然 JavaScript 幾乎可以解決大部分問題,但從無障礙設計的角度、效能跟 bundle size 的角度來看,能夠用 CSS 解決一定是比較好的。然而,盡量不要用 JS 不代表完全不用 JS,這兩者還是有些差別的。這篇文章會重新閱讀一次上面提到的文章,並且點出一些我覺得能夠改善的地方。
透過 :hover
做提示樣式
的確,透過 :hover
來讓使用者知道這個 UI 元件是可互動的,這對前端工程師來說算是基本常識了。文章中還有提到可以透過 :hover
來達到下拉式選單的效果。
- Item1
- Item2
在 hover 時改變 display: block
,一般狀況則是 display: none
。這點看似沒有太大問題,但如果使用者不是使用滑鼠做導航呢?如果使用者透過鍵盤來導航,:hover
是沒有效果的。而且也會侷限在 DOM 的架構裡,要觸發的 UI 元素需要跟下拉列表相鄰。
因此我的建議是在用 :hover
來提示使用者元素可以互動時,要考量如果使用者不是透過滑鼠來導航的話應該要如何互動。包含:
- 額外加上
:focus
或是監聽 click 事件讓使用者觸發下拉式選單 - 加入
aria-expanded
告知螢幕閱讀器目前選單的開啟狀態為何,並加入鍵盤導航讓使用者可以透過上下鍵控制選項
- Item1
- Item2
這個範例裡除了透過 hover 來觸發 Dropdown 之外,也搭配 JavaScript 監聽 focus 以及 mouseover 事件來調整 aria-expanded
。鍵盤導航跟本篇主題不同因此沒有實作。額外一題,如果使用 aria-expanded
的話,也可以將 CSS 調整為:
.dropdown-item:hover + .item,
.item[aria-expanded="true"]
{
/* style */
}
透過 :checked
與相鄰選擇器做客製化樣式
如果要實作客製化的 checkbox 或是 radio,應該會需要用到文章中提到的技巧,透過 pseudo class 加上相鄰選擇器方便實作客製化 checkbox。
使用 :checked
的好處在於我們不需要額外註冊事件監聽器去 toggle 類別,如果要做客製化的 checkbox 或是 radio 的話盡可能優先選擇此做法,而不是從頭用 <div>
做一個,不僅要考慮的東西很多之外,可用性也不一定比得上有點醜但至少能用的 checkbox。
不過用這種方式要注意幾點:
- 使用
aria-label
讓螢幕閱讀器知道這個 checkbox 或 radio 的用途。(或是用aria-labelledby
) - 使用
<div role="status"></div>
或其他方式來通知值的變化(如果需要的話) - 實作 focus 的樣式處理
螢幕閱讀器在閱讀 checkbox 時,只會將 label 名稱跟是否選取唸出來,如果 checkbox 的目的不是選取跟未選取的話(例如暗色主題的切換),另外加入提示會讓螢幕閱讀器較容易理解。
為了讓 input
隱藏但保有 focus 的效果,所以不直接用 display: none
,而是使用其他 CSS 屬性把它隱藏起來。同時為了讓鍵盤導航時有 focus 的樣式,這邊加入了 :focus-visible
,這樣一來只有在使用鍵盤導航(例如 tab)時才會套用規則,點擊的時候就不會看到 focus 的框框跑出來。
多列等高
這篇文章是在 2016 年撰寫的,裡頭講到的方法雖然可行,但有點 old-school,在 2022 年 flex 支援度已經很高的情況下我們可以直接使用 flexbox 來解決,如果要粒度更細一點的話則可以使用 grid。
原理是透過 flex 排版的特性,align-items
在預設情況下會是 stretch
,容器的高度會以同一個 row 當中最高的為主。要注意的地方是如果需要多個 row 的話,記得加入 flex-wrap: wrap
,不然預設情況下 flexbox 會試圖塞進同一個 row 裡頭。
Information
This photo is taken by Unsplash Clay Banks
Tokyo
This photo is taken by Unplash Jezael Melgoza.
I like Tokyo because it's a such good place where I can see people meltdown
表單提交
我很認同作者提到很多人都忽略了其實原生的表單 <form>
已經存在好幾十年了,透過 <form>
來定義表單內容可以省下大量的 JavaScript 程式碼。
裡頭有提到可以透過瀏覽器原生的表單驗證機制搭配 :invalid
pseudo class 來做樣式處理,範例當中是在 invalid 狀態時將繳交按鈕設為 opacity: 0.5
。不過應該是因為範例的關係,作者是使用 <span>
而非 <button>
。實作上應該會使用 <button>
,並透過 JavaScript 在輸入值不合法時加入 disabled
。
如果對 form 還不熟悉的話可以參考下面兩篇文章:
善用 pseudo class
作者有提到像是 :checked
、:focus
、:invalid
等等,善用這些偽類別可以省下不必要的 JavaScript,閱讀起來也會比較輕鬆。
關於 pseudo class 我也有寫一篇文章介紹一些相對比較新的 pseudo class,有興趣的話可以參考。排版時有用的 Pseudo 類別
結語
2017 年時恰好是自己剛入門前端的時期,對細節的處理掌握度不高。現在回頭審視一遍之後發現要實作一個體驗良好的 UI 需要考量到很多細節,不是單純去套用 CSS 就能夠完事的,很多時候為了無障礙設計的考量,JavaScript 往往是不可或缺的角色。