你的網站可以一鍵變色嗎?

若干年前寫過一個叫「網站換色精靈」的小工具,原理是調整網站所有圖片的色相、飽和度和亮度。然而並沒有什麼人用……或許是因為做得不好,又或許這本身就是一種偽需求。

得益於 Web 標準的發展和設計風格的變化,前端開發者從通過切圖還原設計逐漸變為通過代碼還原設計。CSS 預處理器也在一定程度上彌補了 CSS 本身表達能力的不足,許多 UI 框架(比如 Element)將基礎的顏色值作為配置項供使用者定製,其餘的顏色則在它們的基礎上調整亮度/飽和度,或者與其他顏色混合而成。雖說做不到一鍵變色,但是通過重新構建來改變整個網站的配色是沒有問題的。

除了可定製,這樣做還可以讓代碼變得更容易維護。相比較充斥著各種顏色值的 CSS 代碼,甚至可以表達出一些配色思路(或者讓不善於設計的我寫出至少配色上還過得去的 UI)。

配色思路?

下面這段樣式是從七牛的管理控制台中摘抄的:

.btn.btn-primary { color: #1989fa; background-color: rgba(25,137,250,.04); border-color: rgba(25,137,250,.4)}

心算一下(Shift + 左擊),就能發現上面的三個顏色是同一種顏色,只是透明度不同。

由於頁面的背景是純白的,因此調整顏色透明度可以看成是在調整顏色的亮度。按鈕雖然只用了「一種顏色」,但是看起來還是比較和諧的。

從中可以看出對主按鈕常規狀態的設計思路是:

  1. 使用 #1989fa 作為基礎顏色;
  2. 文字顏色使用基礎顏色;
  3. 將基礎顏色調亮 96% 作為背景色;
  4. 將基礎顏色調亮 60% 作為邊框的顏色。

預處理器?

使用像 Sass 這樣的預處理器很容易實現上面的需求:

$primary-color: #1989fa;$text-color: $primary-color;$background-color: scale-color($primary-color, $lightness: 96%);$border-color: scale-color($primary-color, $lightness: 60%);.btn-primary { color: $text-color; background-color: $background-color; border-color: $border-color;}

Sass 會在編譯期間計算出表達式的值,生成這樣的 CSS 代碼:

.btn-primary { color: #1989fa; background-color: #f6faff; border-color: #a3d0fd;}

不過,使用預編譯器就意味著需要構建——總有一些人不喜歡「構建」過程,或者傾向於使用更「原生」的解決方案。

那麼,使用純 CSS 可以在一定程度上實現這樣的效果嗎?答案是肯定的,七牛管理控制台的例子中就用了透明度來實現提升亮度的效果。問題在於,其中的顏色值出現了多次,可維護性還是不高。

CSS 變數

CSS 變數是一項實驗中的技術,不過現代瀏覽器大多都已經支持了,所以如果你的網站面向的用戶使用的基本都是現代瀏覽器,可以考慮使用這項技術。後文嘗試使用這項技術來描述 UI 的配色,編寫更容易維護的純 CSS。

我不打算詳細介紹 CSS 變數,如有興趣可以查閱 MDN 和相關規範。不過不必擔心,即便對 CSS 變數了解不多也沒關係,後文在用到 CSS 變數時會有一些簡單的解釋。

我打算寫一個頁面作為例子。

配色

好吧,作為一個不會設計的前端工程師,我準備找一個現成的顏色主題。在 Adobe Color CC 上最受歡迎的顏色主題里挑了個順眼的,就可以開始配色了。有了顏色主題,配色會容易一些,只需要選 3 ~ 4 種顏色,就可以配出一個不錯的 UI 了。

背景色和文字顏色

為了確保可讀性,只要選出反差和亮度差最大的兩種顏色即可。在這個顏色主題里,自然是前兩個偏黑白的 #323a40 和 #e5eef4 了。我想做一個暗色的配色,因此選擇前者為背景色,後者為文字顏色。

:root { --background-color: #323a40; --text-color: #e5eef4;}

CSS 變數以兩個連字元開頭,定義 CSS 變數與設置屬性類似。上面這段代碼定義了 --background-color 和 --text-color 這兩個 CSS 變數。:root 選擇器會選擇根節點(也就是 <html>),與 html 的區別在於優先順序更高,適合用於定義全局 CSS 變數。

html { background: var(--background-color); color: var(--text-color);}

要引用定義的 CSS 變數也很簡單,只需要使用 var 函數即可。這樣,頁面的背景色和文字顏色就設置好了。

在 JSFiddle 上 DIY

主色

然後,選擇一個主色。主色通常被用在超鏈接、主按鈕、logo 上。為了它們更突出,應該選擇一個與背景色和文字顏色都有一定反差的顏色。這裡,我選擇顏色主題中的第三個顏色 #37b0c0。

:root { --background-color: #323a40; --text-color: #e5eef4; --primary-color: #37b0c0; --input-size: 30px; --input-padding-horizontal: 10px; --button-border-radius: 4px;}button { background: none; border: 1px solid var(--primary-color); border-radius: var(--button-border-radius); color: var(--primary-color); height: var(--input-size); padding-left: var(--input-padding-horizontal); padding-right: var(--input-padding-horizontal); transition: all .15s ease;}button:hover { background: var(--primary-color); color: var(--background-color); cursor: pointer;}

CSS 變數不僅可以定義顏色值,上面的代碼還用 CSS 變數定義了按鈕的大小、內邊距和邊框的半徑。

在 JSFiddle 上 DIY

透明度

CSS 里並沒有像 Sass 里 darken、lighten 那樣的顏色函數,可以考慮使用透明度在一定程度上實現加深或者減淡的效果。不幸的是,CSS 里同樣也沒有操作顏色透明度的函數。我們只能把顏色的三個分量拆開定義:

:root { --background-color-r: 51; --background-color-g: 59; --background-color-b: 64; --background-color: rgb( var(--background-color-r), var(--background-color-g), var(--background-color-b) ); --text-color-r: 229; --text-color-g: 238; --text-color-b: 244; --text-color: rgb( var(--text-color-r), var(--text-color-g), var(--text-color-b) ); --primary-color-r: 62; --primary-color-g: 176; --primary-color-b: 190; --primary-color: rgb( var(--primary-color-r), var(--primary-color-g), var(--primary-color-b) ); --input-size: 30px; --input-padding-horizontal: 10px; --button-border-radius: 4px;}

是的,這麼定義很麻煩。不過,每個顏色值還是只會出現一次。

input, button { --border-color: rgba( var(--text-color-r), var(--text-color-g), var(--text-color-b), var(--border-color-alpha, .3) ); border: 1px solid var(--border-color);}

其中,var(--border-color-alpha, .3) 表示引用 --border-color-alpha 變數的值,如果變數沒有定義或者無效,則回退到 .3。這樣一來,input 和 button 的邊框顏色會變成背景色混合 30% 的文本顏色。

input:focus { --border-color-alpha: .6;}

當焦點在 input 上時,--border-color-alpha 的值將變為 .6,此時邊框顏色會變成背景色混合 60% 的文本顏色。

我使用同樣的方法寫了一個友好的 header。

在 JSFiddle 上 DIY

白天主題

產品經理找到我說,大多數程序員都覺得我做的頁面很友好,但是少數非夜貓子程序員覺得這個主題在白天太刺眼了,希望能有一個「白天主題」。

好在 JavaScript 可以設置 CSS 變數的值,而白天主題只需要把背景顏色和文字顏色互換就可以了。

const themes = [ { name: "dark", scheme: { "--background-color-r": 51, "--background-color-g": 59, "--background-color-b": 64, "--text-color-r": 229, "--text-color-g": 238, "--text-color-b": 244 } }, { name: "light", scheme: { "--background-color-r": 229, "--background-color-g": 238, "--background-color-b": 244, "--text-color-r": 51, "--text-color-g": 59, "--text-color-b": 64 } }];let currentTheme = 0;window.nextTheme = function () { currentTheme = (currentTheme + 1) % themes.length; const theme = themes[currentTheme]; Object.keys(theme.scheme).forEach(name => { const value = theme.scheme[name]; document.documentElement.style.setProperty(name, value); });}

在 JSFiddle 上 DIY

顏色混合

透明度不能解決所有問題,如果需要和另一種顏色混合(單純與黑白混合可以考慮使用 HSL 模型),或者需要漸變,就只能使用一些「黑科技」了。

比如說,想把背景顏色設置為 50% 文字顏色 + 50% 主色:

... { --base-color: var(--text-color); --mix-color: rgba( var(--primary-color-r), var(--primary-color-g), var(--primary-color-b), .5 ); background-color: var(--base-color); background-image: linear-gradient( to bottom, var(--mix-color), var(--mix-color) );}

已知問題

除了用起來不如 CSS 預處理器方便之外,Safari 在某些情況下無法工作。比如說

:root { --r: 255; --g: 0; --b: 0;}.foo { border: 10px solid rgba(var(--r), var(--g), var(--b), .5);}

在 Safari 下邊框會被渲染為 currentColor 而不是半透明的紅色。

解決方法很簡單,在內部多定義一個 CSS 變數即可。

在 JSFiddle 上 DIY(請對比在 Chrome 中和 Safari 中的表現)

推薦閱讀:

帶你入門 CSS Grid 布局
更快的火狐!超快速 CSS 引擎:Quantum CSS
【修真院「純潔」系列之一】四臉蒙逼
張鑫旭:說說CSS學習中的瓶頸

TAG:CSS | 前端开发 | JavaScript |