替公司的元件庫補深色模式時,我接手過一份只寫 HEX 的色值規範。設計同事要求「hover 態就是主色深一點點」,聽起來是半句話的事,我卻在那個藍色上反覆對不齊:手工把 #1E90FF 換算成 HSL 時飽和度算成了 88%,改完亮度再轉回 HEX,已經變成另一個顏色。同一顆按鈕在設計稿和網頁上差了一檔,評審時被當成「還原度問題」來回扯了兩天。顏色格式本身不難,難的是三套寫法之間來回換算時人手最容易出錯的那幾步。
一、三種寫法描述的是同一個東西,只是座標軸不同
HEX 和 RGB 是同一套「紅綠藍三通道」的兩種記法,HSL 換了一組更貼近直覺的軸:色相、飽和度、亮度。同一種藍的四種寫法放在一起是這樣:
| 格式 | 寫法 | 這一種藍的取值 | 分量範圍 | 什麼時候用它 |
|---|---|---|---|---|
| HEX | #RRGGBB | #1E90FF | 每兩位 00–FF | 設計稿標註、CSS 變數、品牌色文件 |
| RGB | rgb(r,g,b) | rgb(30,144,255) | 各通道 0–255 | 需要參與計算、和 JS 打交道 |
| HSL | hsl(h,s%,l%) | hsl(210,100%,56%) | H 0–360,S/L 0–100% | 衍生深淺、做主題色階 |
| RGBA | rgba(r,g,b,a) | rgba(30,144,255,0.5) | A 為 0–1 | 遮罩層、懸浮態半透明 |
主題色體系裡 HSL 更省事的原因就在這:只動 L,H 和 S 保持不變,得到的就是同色相的深淺一檔。上面那個藍把亮度從 56% 壓到 46%,得到約 rgb(0,117,235),也就是接近 #0075EB 的深色,正好用作 hover。
二、手算為什麼會錯:兩個公式的必經中間量
HEX 轉 RGB 是無腦的,兩位十六進位查表即可:1E=30、90=144、FF=255。問題出在 RGB 到 HSL,必須先拿到最大值和最小值:
- 亮度
L = (max + min) / 2。那個藍裡 max=255、min=30,(255+30)/2/255 ≈ 56%。 - 飽和度
S = (max − min) / (1 − |2L − 1|),分母裡的|2L−1|是所有人漏掉的那一項。我當初直接用了(max−min)/max,算出 88%,而正確值是 100%。 - 色相要按「哪個通道最大」分三段選公式,算出來還可能是負數,得加 360 再取模。這一步手算幾乎必錯。
反方向從 HSL 回 HEX 還要再經過 chroma 和中間量,兩套公式互推一次就是十幾次乘除。顏色這件事上,手算的性價比極低——我現在都是在顏色轉換裡輸入任一種格式,讓三種值同時出來,再複製需要的那種。
三、帶透明度時最坑的一點:8 位 HEX 的位元組順序
CSS 裡的 8 位十六進位是 #RRGGBBAA,透明度在最後:rgba(30,144,255,0.5) 寫成 HEX 是 #1E90FF80(0.5 × 255 ≈ 128 = 80)。但 Android 的顏色整數、不少設計外掛和圖片格式用的是 #AARRGGBB,透明度在最前。同一個數值挪了位置,讀出來就是完全另一個顏色:把 #1E90FF80 當成 #AARRGGBB 解析,透明度取的是 0x1E,只有約 12%,剩下的三個位元組被讀成 rgb(144,255,128)——本來該是半透明藍的遮罩層,結果變成一層幾乎看不見的淺綠。我那次半透明遮罩層在 iOS 端一片怪色,就是栽在這個順序上。跨端傳色值時,寧可多寫一行註解標明通道順序。
四、色值定了之後,還要看它和背景能不能讀清
WCAG 對正文與背景要求的對比度是 4.5:1,大字級文字放寬到 3:1。拿 #1E90FF 配純白算,相對亮度約 0.27,對比度約 3.2:1——在白底上放小字其實不合格;換成壓暗一檔的 #0075EB,對比度升到約 4.4:1,仍差一點點,得再把 L 往下調幾檔。這類判斷不需要儀器,需要的是別把「好看」當成「看得清」。
還有兩個會偷偷改變色值的環節:一是取色來源,螢幕截圖取色時如果圖片帶 ICC 設定或做過壓縮,讀到的 RGB 會和設計稿差 1–3 個單位;二是螢幕,普通辦公螢幕覆蓋 sRGB 不足時,飽和度高的綠和藍會明顯偏灰。規範落地時我更傾向固定 HEX,HSL 只作為衍生工具使用。
五、常見疑問
Q:#fff 這種三位寫法是什麼?
是 #ffffff 的縮寫,規則是每一位都恰好重複兩次才行。#1E90FF 不能縮寫,#FF00AA 可以寫成 #F0A。
Q:HSL 裡的 H 為什麼到 360 又回到紅色?
因為色相是一個圓:0 紅、60 黃、120 綠、180 青、240 藍、300 洋紅,360 與 0 重合。做同色系衍生時讓 H 加減十幾度就是鄰近色,加 180 度是互補色。
Q:L 調到 0 和 100 會發生什麼?
L=0 是純黑、L=100 是純白,此時 S 和 H 都不再影響結果。所以「給一個色加亮」調到 90% 以上就會開始失去色相,深色模式裡的字色通常停在 70%–90% 之間。
Q:主題變數到底存 HEX 還是 HSL?
看你要改哪個維度。只換深淺、做亮暗兩套主題,存 HSL 更好算;只做整體替換品牌色、且要和設計稿一一對齊,存 HEX 更直觀。兩者可以無損互轉,不必擔心後期改不回來。
Q:從介面截圖裡取的色為什麼和規範對不上?
截圖鏈路會做色彩管理和壓縮,取到的值可能有 1–3 的偏差。要精確值時優先從設計檔案裡的標註或 CSS 變數本身取,再順手換算成需要的格式。
現在那個元件庫的規範是兩層:HEX 作為唯一權威值,HSL 只用來生成狀態色並在文件裡附一行換算結果。當初那兩天的來回,本質上是把「深一點」這種模糊描述直接交給了兩種座標系——把換算變成一次輸入輸出的固定動作之後,這類分歧就不再需要開會解決了。