CSS color-mix() 與相對色彩語法:淺色、hover 與透明度
color-mix() 會在你指定的色彩空間裡混合兩個顏色,相對色彩語法則讓你修改現有顏色的單一通道,例如 oklch(from var(--brand) calc(l - 0.1) c h)。兩者都在瀏覽器中執行、可搭配 CSS 變數,也能取代大部分 Sass 色彩函式。做淺色和 hover 時在 oklab 裡混色,做透明效果時用 srgb。
閱讀時間約 2 分鐘
color-mix() 如何運作
color-mix() 接收一個色彩空間和兩個顏色(百分比可省略),回傳兩者之間的顏色。color-mix(in oklab, var(--brand) 90%, black) 代表 90% 品牌色加 10% 黑色,在 Oklab 中混合。只寫一個百分比時,另一個顏色會拿到剩下的部分;都不寫時,各占 50%。
瀏覽器在執行時才計算結果,所以能搭配自訂屬性、currentColor 和主題切換。Sass 函式只在建置時執行一次,看不到 var() 的值。
如果兩個百分比加起來不到 100%,結果也會變成部分透明:color-mix(in srgb, red 30%, blue 30%) 是均勻混合、不透明度 60% 的顏色。除非你就是要這個效果,否則讓總和保持 100%。
CSS
.button {
--brand: #2563eb;
background: var(--brand);
}
.button:hover {
background: color-mix(in oklab, var(--brand) 90%, black);
}
.badge {
background: color-mix(in oklab, var(--brand) 10%, white);
border-color: color-mix(in srgb, var(--brand) 30%, transparent);
}該在哪個色彩空間混色:srgb、oklab 還是 oklch
色彩空間對結果的影響比多數人想像的大。下面是純藍 #0000ff 和純黃 #ffff00 以 50/50 混合的結果,計算方式和瀏覽器完全相同。
在 srgb 中,各通道平均剛好是 128、128、128:混出來是 #808080,彩度為零的灰色。Oklab 也是沿著直線混合,而藍色和黃色分別位在直線兩端,所以大多互相抵消,變成暗淡的灰藍色。Oklch 則是對色相角度做內插,從 264° 走捷徑到 110°,中點是色相 187° 的鮮豔藍綠色,保留了兩個輸入顏色的彩度。
- 藍色 #0000ff#0000ff
- 黃色 #ffff00#ffff00
- 在 srgb 中:#808080#808080
- 在 oklab 中:#6cabc7#6cabc7
- 在 oklch 中:#00bdb0#00bdb0
| 色彩空間 | 藍色 + 黃色 | OKLCH L, C | blue-600 + yellow-400 | OKLCH L, C |
|---|---|---|---|---|
| srgb | #808080 | 0.600, 0 | #909880 | 0.665, 0.035 |
| oklab | #6cabc7 | 0.710, 0.077 | #90a3ad | 0.703, 0.026 |
| oklch | #00bdb0 | 0.710, 0.262 | #00bd9e | 0.703, 0.194 |
第二組是 Tailwind 的 #2563eb 和 #facc15。oklch 的結果超出 sRGB,所以表中顯示的是色域映射前的彩度,HEX 則是 sRGB 螢幕能顯示的最接近顏色。
有兩點特別明顯。srgb 的亮度落在 0.600,但藍色(0.452)和黃色(0.968)在感知上的中點是 0.710:這就是 srgb 漸層中間又暗又髒的原因。而只有 oklch 能讓混色保持鮮豔,代價是會經過一個兩個輸入顏色都沒有的色相。
經驗法則:做淺色、深色和 hover 狀態時用 oklab,因為你停留在同一個色相上,想要均勻的亮度階層。兩個顏色都很飽和、而你也想要飽和的結果時,用 oklch。做透明效果時用 srgb,它和瀏覽器合成圖層的方式一致。
實用寫法:hover、淺色與透明版本
以下所有數值都以 #2563eb 作為 --brand。白色文字放在它上面的對比值是 5.17:1。
混入 10% 黑色時,oklab 比 srgb 暗得更多:白色文字的對比值提高到 6.50:1,srgb 則是 6.11:1。第二個顏色是黑或白時,oklab 和 oklch 的結果相同,因為黑白沒有色相可以內插。
| 寫法 | CSS | 結果 | 對比 |
|---|---|---|---|
| hover,暗 10% | color-mix(in oklab, var(--brand) 90%, black) | #1f55cc | 白色文字 6.50:1 |
| active,暗 20% | color-mix(in oklab, var(--brand) 80%, black) | #1947ae | 白色文字 8.22:1 |
| 在 srgb 中混出的 hover | color-mix(in srgb, var(--brand) 90%, black) | #2159d3 | 白色文字 6.11:1 |
| 淺色背景,10% | color-mix(in oklab, var(--brand) 10%, white) | #e8f0ff | 品牌色文字 4.51:1 |
| 淺色背景,20% | color-mix(in oklab, var(--brand) 20%, white) | #d2e2ff | 品牌色文字 3.95:1 |
| 透明,20% | color-mix(in srgb, var(--brand) 20%, transparent) | rgba(37, 99, 235, 0.2) | 在白底上看起來像 #d3e0fb |
淺色要特別留意。品牌色文字在白底上是 5.17:1,在 10% 淺色上是 4.51:1,在 20% 淺色上只剩 3.95:1,一般文字就達不到 WCAG AA。如果要把品牌色文字放在品牌淺色背景上,文字請改用 hover 的深色版本。
和 transparent 混合時,顏色本身不變,只降低 alpha,因為瀏覽器混合的是預乘後的數值。結果會隨背後的內容而變:同一條規則在白底上看起來像 #d3e0fb,在深色的 #0f172a 背景上則像 #132651。和白色混出的淺色在深色頁面上仍然是淡淡的一塊,所以同時出現在淺色與深色主題的元件,通常用透明版本比較好。
相對色彩語法:只改一個通道
相對色彩語法會把現有顏色拆成各個通道,讓你用 calc() 改寫其中任何一個。在 oklch(from var(--brand) l c h) 裡,關鍵字 l、c、h 分別代表 --brand 的亮度(0 到 1)、彩度和色相。
和 color-mix() 不同,它只改變一個屬性:降低亮度而不動彩度、旋轉色相而不改變亮度,或是替以 HEX 儲存的顏色加上 alpha。
| 寫法 | CSS | 由 #2563eb 得到的結果 | 白色文字 |
|---|---|---|---|
| 更深 | oklch(from var(--brand) calc(l - 0.1) c h) | #0442c8 | 8.02:1 |
| 更淺 | oklch(from var(--brand) calc(l + 0.1) c h) | #4484ff | 3.51:1 |
| 彩度減半 | oklch(from var(--brand) l calc(c * 0.5) h) | #4e6faf | 4.98:1 |
| 色相 +30° | oklch(from var(--brand) l c calc(h + 30)) | #7a49df | 5.46:1 |
| 互補色 | oklch(from var(--brand) l c calc(h + 180)) | #956700 | 4.98:1 |
| 同亮度的灰色 | oklch(from var(--brand) l 0 h) | #707070 | 4.95:1 |
| 50% alpha | oklch(from var(--brand) l c h / 0.5) | rgba(37, 99, 235, 0.5) | 取決於背景 |
CSS
:root {
--brand: #2563eb;
--brand-hover: oklch(from var(--brand) calc(l - 0.08) c h);
--brand-muted: oklch(from var(--brand) l calc(c * 0.5) h);
--brand-ring: oklch(from var(--brand) l c h / 0.4);
--accent: oklch(from var(--brand) l c calc(h + 30));
}看看保留 l 的那四列:改變彩度或色相時,白色文字的對比值只在 4.95:1 到 5.46:1 之間變動,原色則是 5.17:1。決定對比的是亮度,所以用這種方式做出的色相變化,無障礙程度大致相同。更淺和互補色這兩個顏色在完整彩度下超出 sRGB,瀏覽器會把它們映射到能顯示的最接近顏色。
color-mix()、相對色彩語法與 Sass darken() 比較
當你想的是「加一點黑」或「透明一點」時,用 color-mix()。當你用通道思考,例如「同一個顏色,暗 0.1」,就用相對色彩語法。兩者都能取代大部分 Sass 色彩函式,切換主題時也不必重新建置。
| color-mix() | 相對色彩語法 | Sass darken() | |
|---|---|---|---|
| 執行時機 | 在瀏覽器中 | 在瀏覽器中 | 建置時 |
| 可搭配 var() 和 currentColor | 可以 | 可以 | 不行 |
| 色彩模型 | 你指定的空間:srgb、oklab、oklch、hsl 等 | 任何色彩函式:oklch()、rgb()、hsl()、lab() | 只有 HSL |
| 最適合 | 淺色、深色、透明度、混合兩個設計變數 | 修改單一通道:亮度、彩度、色相或 alpha | 使用固定顏色的舊程式碼 |
| 調整幅度 | 到第二個顏色距離的一部分 | 精確,以該通道的單位計算 | 固定的 HSL 點數,肉眼看來不均勻 |
| 支援起始版本 | Chrome 111、Safari 16.2、Firefox 113 | Chrome 119、Safari 18、Firefox 128 | 所有瀏覽器,輸出是靜態值 |
為什麼 Sass darken() 的變化不均勻
darken($color, 10%) 會把 HSL 亮度減去 10 點。HSL 亮度不等於感知亮度,所以肉眼看到的變化取決於色相。下面在四個常見的介面顏色上比較 darken(10%) 和 oklch(from … calc(l - 0.1) c h),以 ΔE OK 衡量,約 0.02 就是人眼能分辨的最小差異。
| 顏色 | Sass darken(10%) | ΔE OK | OKLCH l − 0.1 | ΔE OK |
|---|---|---|---|---|
| #2563eb | #134cca | 0.078 | #0442c8 | 0.099 |
| #facc15 | #d7ad05 | 0.099 | #d9ab00 | 0.101 |
| #16a34a | #107636 | 0.136 | #00842b | 0.094 |
| #dc2626 | #b21d1d | 0.090 | #b70000 | 0.089 |
同樣的 darken(10%),綠色的變化是藍色的 1.7 倍。OKLCH 版本在每個色相上都維持在 0.089 到 0.101 之間;這點小差異來自色域映射,因為有些變暗後的顏色剛好落在 sRGB 之外。Sass 官方也建議用 color.adjust() 或 color.scale() 取代 darken(),而在純 CSS 裡兩者都用不到。
瀏覽器支援與備援寫法
color-mix() 可在 Chrome 和 Edge 111、Safari 16.2 以及 Firefox 113 中使用,這些版本都在 2023 年推出。相對色彩語法在 Chrome 和 Edge 119、Firefox 128 加入;Safari 在 16.4 推出早期版本,Safari 18 才支援目前的語法。所有現行瀏覽器都支援這兩者。
常見的技巧是在前一行先寫一個普通值,但當新值裡含有 var() 時就行不通。含有 var() 的宣告在解析時一定會被接受,所以瀏覽器會丟掉你的備援那一行;如果它算不出 color-mix(),就會退回繼承值或初始值。請改用 @supports 包住新寫法。
CSS
.button:hover {
background: #1f55cc;
}
@supports (color: color-mix(in oklab, red, blue)) {
.button:hover {
background: color-mix(in oklab, var(--brand) 90%, black);
}
}
@supports (color: oklch(from red l c h)) {
.badge {
border-color: oklch(from var(--brand) l c h / 0.3);
}
}常見問題
CSS 的 color-mix() 有什麼用?
color-mix() 會在你指定的色彩空間裡混合兩個顏色並回傳結果,例如 color-mix(in oklab, var(--brand) 90%, black) 會得到比品牌色暗 10% 的顏色。它在瀏覽器中執行,所以能搭配 CSS 變數和 currentColor。
color-mix() 該用哪個色彩空間?
做淺色、深色和 hover 狀態時用 oklab,因為它的亮度階層均勻,而且能保留色相。混合兩個飽和色、也想要飽和的結果時用 oklch;和 transparent 混合時用 srgb。在 srgb 中混合藍色和黃色會得到平淡的灰色 #808080,oklch 則得到鮮豔的藍綠色。
如何用純 CSS 在 hover 時讓顏色變深?
使用 background: color-mix(in oklab, var(--brand) 90%, black) 或 background: oklch(from var(--brand) calc(l - 0.1) c h)。以 #2563eb 為例,前者得到 #1f55cc,後者得到 #0442c8,白色文字的對比值分別從 5.17:1 提高到 6.50:1 和 8.02:1。
如何讓 CSS 變數中的顏色變透明?
寫 color-mix(in srgb, var(--brand) 20%, transparent) 或 oklch(from var(--brand) l c h / 0.2)。即使 --brand 是以 HEX 儲存,兩種寫法都能用,而且顏色本身不變,只降低不透明度。
所有瀏覽器都支援 color-mix() 嗎?
是的,所有現行瀏覽器都支援:Chrome 和 Edge 從 111 起、Safari 從 16.2 起、Firefox 從 113 起。若要照顧舊版瀏覽器,先寫一個靜態顏色,再把 color-mix() 版本放進 @supports,因為含有 var() 的那一行無法自行退回備援值。
color-mix() 和相對色彩語法有什麼不同?
color-mix() 依百分比混合兩個顏色,適合做淺色、深色和透明度。相對色彩語法,例如 oklch(from var(--brand) l c calc(h + 30)),會改寫單一顏色的個別通道,適合精確的亮度階層、色相偏移和 alpha。