logo
  • 現在做什麼
  • 關於我

Kalan

文章分類

  • 前端
  • 開發筆記
  • 雜談
  • 年度回顧

快速連結

  • 現在做什麼
  • 關於我
  • 聯絡我
  • 職涯思考🔗

關注我

在福岡生活的開發者,分享軟體開發與日本生活的點點滴滴。

© 2025 Kalan Made with ❤️. All rights reserved.

HTML 和 CSS 能解決很多問題,但 JS 也很重要

由愷開愷開撰寫2022年11月27日 9:57
首頁/前端
💡

如果想問問題或單純回饋的話可以填寫表單唷

English日文

目錄

  1. 透過 做提示樣式
  2. 透過 與相鄰選擇器做客製化樣式
  3. 多列等高
  4. 表單提交
  5. 善用 pseudo class
  6. 結語

在 2017 年時曾經讀過一篇文章 – Effective前端1:能使用html/css解决的问题就不要使用JS,第一次閱讀時覺得深有同感,也學到很多當時還不熟的技巧,很推薦大家看看。雖然 JavaScript 幾乎可以解決大部分問題,但從無障礙設計的角度、效能跟 bundle size 的角度來看,能夠用 CSS 解決一定是比較好的。然而,盡量不要用 JS 不代表完全不用 JS,這兩者還是有些差別的。這篇文章會重新閱讀一次上面提到的文章,並且點出一些我覺得能夠改善的地方。

透過 :hover 做提示樣式

的確,透過 :hover來讓使用者知道這個 UI 元件是可互動的,這對前端工程師來說算是基本常識了。文章中還有提到可以透過 :hover 來達到下拉式選單的效果。

.dropdown { position: relative; display: inline-flex; justify-content: space-around; gap: 1em; } .item { display: none; } .dropdown-item:hover + .item { display: block; position: absolute; top: 30px; left: 0 padding: 10px; background-color: #efefef; }
聯絡我
  • Item1
  • Item2

在 hover 時改變 display: block,一般狀況則是 display: none。這點看似沒有太大問題,但如果使用者不是使用滑鼠做導航呢?如果使用者透過鍵盤來導航,:hover是沒有效果的。而且也會侷限在 DOM 的架構裡,要觸發的 UI 元素需要跟下拉列表相鄰。

因此我的建議是在用 :hover 來提示使用者元素可以互動時,要考量如果使用者不是透過滑鼠來導航的話應該要如何互動。包含:

  • 額外加上 :focus 或是監聽 click 事件讓使用者觸發下拉式選單
  • 加入 aria-expanded 告知螢幕閱讀器目前選單的開啟狀態為何,並加入鍵盤導航讓使用者可以透過上下鍵控制選項
.dropdown { position: relative; display: inline-flex; justify-content: space-around; gap: 1em; } .item { display: none; } .dropdown-item:hover + .item, .dropdown-item:focus + .item { display: block; position: absolute; top: 30px; left: 0; padding: 10px; background-color: #efefef; }
  • Item1
  • Item2
function toggleExpanded() { const node = document.querySelector('#dropdown-1'); const prev = node.getAttribute('aria-expanded'); node.setAttribute('aria-expanded', prev === 'true' ? 'false' : 'true'); } const toggle = document.querySelector('#toggle'); toggle.addEventListener('focus', toggleExpanded); toggle.addEventListener('blur', toggleExpanded); toggle.addEventListener('mouseover', toggleExpanded); toggle.addEventListener('mouseleave', toggleExpanded);

這個範例裡除了透過 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 的目的不是選取跟未選取的話(例如暗色主題的切換),另外加入提示會讓螢幕閱讀器較容易理解。

.label:has(input:focus-visible) { outline: 2px solid blue; } .track { position: relative; display: inline-block; width: 50px; height: 1.2rem; border-radius: 9999px; background-color: #778da9; cursor: pointer; } .track .cursor { position: absolute; left: 0; top: 1px; display: inline-block; width: 1.1rem; height: 1.1rem; border-radius: 50%; border: 1px solid #efefef; transition: transform 0.3s ease-in; background-color: #fff; } .checkbox .cursor { transform: translateX(0); } .checkbox:checked~.track .cursor { transform: translateX(1.3rem); } .checkbox:checked~.track { background-color: #778da9; } /* keep hidden but focusable */ .hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; }
toggle.addEventListener('change', function(e) { const node = document.getElementById('status') node.innerHTML = '目前主題已切換' })

為了讓 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 裡頭。

.container { max-width: 1200px; width: 95%; margin: 0 auto; display: flex; /* comment me */ flex-wrap: wrap; justify-content: flex-start; gap: 1em; } .card { width: 30%; // display: inline-block; /* comment me */ padding: 8px; background-color: #efefef; } .card img {max-width: 100%}

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 還不熟悉的話可以參考下面兩篇文章:

  • 一起理解 HTML 當中的 Form Data
  • form 標籤與 FormData 的應用

善用 pseudo class

作者有提到像是 :checked、:focus、:invalid 等等,善用這些偽類別可以省下不必要的 JavaScript,閱讀起來也會比較輕鬆。

關於 pseudo class 我也有寫一篇文章介紹一些相對比較新的 pseudo class,有興趣的話可以參考。排版時有用的 Pseudo 類別

結語

2017 年時恰好是自己剛入門前端的時期,對細節的處理掌握度不高。現在回頭審視一遍之後發現要實作一個體驗良好的 UI 需要考量到很多細節,不是單純去套用 CSS 就能夠完事的,很多時候為了無障礙設計的考量,JavaScript 往往是不可或缺的角色。

← 排版時有用的 Pseudo 類別2022 Advent Of Code: Cathode-Ray Tube →

如果覺得這篇文章對你有幫助的話,可以考慮下面的連結請我喝一杯 ☕ 可以讓我平凡的一天變得閃閃發光 ✨

☕Buy me a coffee

目錄

  1. 透過 做提示樣式
  2. 透過 與相鄰選擇器做客製化樣式
  3. 多列等高
  4. 表單提交
  5. 善用 pseudo class
  6. 結語