透過 CSS Variable 取代 Sass 並設計一個臻至完美的動態色彩主題系統
前後端工程師,善用 JavaScript、HTML 5、CSS 3 和 Golang 與 PHP、Node.js 、C#。目前正為台灣的社群網站進行趕工,然而這東西卻趕了很久。

色彩佈景系統在前端實作時常常都是個不小的問題,直到 Sass、Less 這種預先處理器的出現才解決了不少的麻煩,因為你能夠透過短短幾行程式碼就讓程式幫你自動處理顏色的部份,而這方面的教學在國外網站中也不是少數。

在這裡以 Sass 舉個例子(範例程式碼會用 Scss,因為可換行提供更高的閱讀性),我們常常會定義一個主題調色盤,然後裡面有著不同的色調,還有色調的文字、背景色。如果你已經耐不住性子了,可以直接到這個 Gist 中檢視原始碼。

$theme-colors: (
    '': (
        'text-color'            : #333,
        'background-color'      : #F5F5F5,
        'background-hover-color': #EEE,
    ),
    '.primary': (
        'text-color'            : #FFF,
        'background-color'      : #348CEE,
        'background-hover-color': #3186E4,
    ),
); 

想當然我們沒有必要自己手動替這些色調逐一新增 CSS 樣式定義。透過 Sass 的 @each 函式就能夠循環調色盤中的每一個色調,並且使用其中的顏色。

@each $name, $colors in $theme-colors {
    button#{$name} {
        color     : map-get($colors, text-color);
        background: map-get($colors, background-color);
    }
    button#{$name}:hover {
        background: map-get($colors, background-hover-color);
    }
};

而最終的成果就顯得十分完美了,當我們定義好之後上述程式碼並且執行之後,一個按鈕就會自動擁有主題調色盤中的所有色調(包含預設定義色),看起來十分地完美!

button {
    color     : #333;
    background: #F5F5F5;
}
button:hover {
    background: #EEE;
}
button.primary {
    color     : #FFF;
    background: #348CEE;
}
button.primary:hover {
    background: #3186E4;
}

但…現實並沒有想像中的這麼美好,還記得嗎?我們把樣式定義寫在一個 @each 循環裡,這意味著你沒有辦法在其中定義按鈕的基本外觀,因為在裡面定義的話就會發生重複的樣式,而折衷方案就是將色彩與外觀樣式分開定義,這顯得沒那麼直覺了。

透過 CSS Variable 簡化並解決一切

既然如此,有什麼方法能夠同時定義外觀樣式又能夠定義色彩呢? 答案就是更方便的 CSS Variable(也稱:CSS 變數)。CSS Variable 是一個能夠在父容器定義並在子容器使用的動態變數,當你更改父容器的變數定義,子容器的樣式就會跟著異動。

和定義基本的 CSS 外觀屬性差不多,但開頭需要有兩個減號(--),就像下方的程式碼那樣。然後透過 var() 函式取得指定的變數內容。

.section {
    --text-color: #FFF;
}

.section button {
    color: var(--text-color); /** #FFF */
}

現在讓我們將目光轉回到色彩佈景主題上;先在什麼特別樣式都沒有的按鈕上定義顏色變數,而這就是按鈕最原始的基本色調。然後額外定義另一個 .primary 色調,當一個按鈕被套用這個色調類別的時候,其中的顏色變數就會自動改變,這讓你不用特別去更改不同色調的樣式屬性。

button {
    --text-color            : #333;
    --background-color      : #F5F5F5;
    --background-hover-color: #EEE;
}

button.primary {
    --text-color            : #FFF;
    --background-color      : #348CEE;
    --background-hover-color: #3186E4;
}

接著將剛才定義的顏色變數套用到按鈕外觀定義上,因為按鈕已經不再由 @each 循環迴圈包覆了,所以定義顏色的同時也可以順便定義外觀樣式而不需要額外拆開。現在你的按鈕會依照不同的色調類別而載入不同的顏色變數,太好了!

button {
    /** 你甚至可以邊定義顏色邊定義外觀樣式,耶! */
    display   : block;
    color     : var(--text-color);
    background: var(--background-color);
}

button:hover {
    background: var(--background-hover-color);
}

當然,如果你想要的話也可以直接把預設顏色定義上方程式碼的 button 裡面作為預設值,但為了淺顯易懂我還是先把他們分開了。

動態支援瀏覽器的亮暗色系設定

透過新的 prefers-color-scheme 媒體查詢語句,我們可以很清楚地知道使用者的瀏覽器偏好的是亮色還是暗色系主題,並且以此使用不同的 CSS 外觀樣式。以往在 Sass 中,你還會需要在定義顏色的時候額外加上一堆重複的 @media 媒體查詢語句,而且你可能還需要額外處理 dark- 的變數。

@each $name, $colors in $theme-colors {
    button#{$name} {
        color     : map-get($colors, text-color);
        background: map-get($colors, background-color);
        @media (prefers-color-scheme: dark) {
            color     : map-get($colors, dark-text-color);
            background: map-get($colors, dark-background-color);
        }
    }
    button#{$name}:hover {
        background: map-get($colors, background-hover-color);
        @media (prefers-color-scheme: dark) {
            background: map-get($colors, dark-background-hover-color);
        }
    }
};

噢不…這看起來真是太糟糕了,讓我們看看 CSS Variable 可以怎麼幫助我們解決這個問題。答案很簡單,就是在暗色系時更改原本的顏色變數內容而不是新增一個暗色系專屬的顏色變數。

/** 當瀏覽器偏好暗色系時,會採用這邊的顏色。 */
@media (prefers-color-scheme: dark) {
    button {
        --text-color      : #000;
        --background-color: #FFF;
    }
}

button {
    --text-color      : #FFF;
    --background-color: #000;

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

這樣一個完美且簡易、不需要重複定義過多選擇器的純 CSS 動態主題系統就已經完成了。如果想要測試亮色與暗色系則可以透過 Chrome 的 Rendering 設置面板下方的 Emulate CSS media feature prefers-color-scheme 來更改瀏覽器目前偏好的色系設置。

在 Windows 裡按下 F12 並且輸入「>Render」就可以開啟 Chrome 的渲染除錯面板。

透過 CSS Variable 你的樣式屬性將不再是像以往那樣由 Sass 所渲染的死板內容,因為你可以在任何時候替換其中的變數內容,而且還能與 JavaScript 進行互動!

當初在製作一個車用儀表板專案的時候,因為透過 JavaScript 翻轉一個圖片太過於麻煩,所以就直接在 CSS 裡設置 transform: rotate(--my-rotation) 才意識到 CSS Variable 是個多麼有用的東西,希望這篇文章能夠幫助你不少。