(S)CSS中實現主題樣式的4½種方式 [譯]

weixin_34148340發表於2018-12-11

原Slides: 4½ Methods for Theming in (S)CSS ; 作者: Harry Roberts

PM說要實現一個一鍵設定主題的功能,作為技術,你能想到的實現方式有哪些呢?

1. 什麼是主題樣式?

相信大家對網頁的主題樣式功能肯定不陌生。對於一些站點,在基礎樣式上,開發者還會為使用者提供多種主題樣式以供選擇。

下面就是一個主題樣式功能:使用者可以在右側選擇自己喜歡的主題色,從而得到一個“個性”的頁面。

還有時候,我們開發了一個系統用來售賣,採購我們系統的客戶可能有多個。也許其中一個客戶很喜歡我們當前的深色色系主題,但是另一個系統的採購方希望我們能為它們定製一套新的樣式。他們希望買來的系統能貼合它們自己的品牌調性,變為淺色的。這其實也是一種主題樣式的需求。

在上面的討論裡,除了“主題”外,我們又引出了一個概念——個性化。經常,我們說到主題時,還會有一種說法叫做:個性化主題。這兩者在英文中分別有兩個對應的詞: Theming 與 Customisation。

當我們說主題(Theming)與個性化定製(Customisation)的時候,很多時候其實並沒有區分兩者。但實際上,兩者還是有一些微妙的區別的。

1.1. 主題 Theming 與個性化定製 Customisation 的區別

我們說的主題(Theming)與個性化定製(Customisation)的時候,還是有一些微妙的區別的。

主題:由開發者定義

主要表現在:

  • 系統的輸入是由開發者定義的
  • 一般來說具有有限的種類
  • 具有已知的規則與常量

例如,我們常見的一些應用會提供夜間主題、閱讀模式,這些也算是主題(Theming)的範疇。

個性化定製:由使用者定義

特點表現在:

  • 系統的輸入是由使用者來提供
  • 一般具有無限種可能
  • 規則更靈活,使用者“為所欲為”

可以看到,“個性化”其實更強調了使用者對系統的的影響力。

很多時候,我們談到“主題”與“個性化定製”時,也許並沒有一個明確的邊界。從上面的描述也可以看出,兩者似乎是處於天平的兩端,區別主要在於開發者對規則的控制力度以及所能實現的差異化的粒度。

而我們更多的是在兩點之間找到一個平衡點。

1.2. 對實現“主題功能”的建議

我們已經對主題樣式有了初步的瞭解,如果你也在產品中遇到了主題樣式的相關需求,不妨先看看以下幾點建議:

  1. 儘可能避免這個功能。因為很多時候這可能只是個偽需求。
  2. KISS原則(Keep It Simple, Stupid!)。儘可能降低其複雜性。
  3. 儘量只去改變外觀,而不要改動元素盒模型(box-model)。
  4. 嚴格控制你的規則,避免預期外的差異。
  5. 把它作為一個錦上添花的功能來向上促銷(up-sell)。

2. 實現“主題樣式”的方式

2.1. 方式一:Theme Layer

Overriding default style with additional CSS.

這應該是實現主題功能的一種最常用的手段了。首先,我們的站點會有一個最初的基礎樣式(或者叫預設樣式);然後通過新增一些後續的額外的CSS來覆蓋與重新定義部分樣式。

具體實現

首先,我們引入基礎的樣式 components.* 檔案

@import "components.tabs";
@import "components.buttons"
複製程式碼

其中 components.tabs 檔案內容如下

.tab {
    margin: 0;
    padding: 0;
    background-color: gray;
}
複製程式碼

然後,假設我們的某個主題的樣式檔案存放於 theme.* 檔案:

對應於 components.tabstheme.tabs 檔案內容如下

.tab {
    background-color: red;
}
複製程式碼

因此,我們只需要引入主題樣式檔案即可

@import "components.tabs";
@import "components.buttons"

@import "theme.tabs";
複製程式碼

這樣當前的樣式就變為了

.tab {
    margin: 0;
    padding: 0;
    /* background-color: gray; */
    background-color: red;
}
複製程式碼

優點

  • 實現方式簡單
  • 可以實現將主題應用與所有元素

缺點

  • 過多的冗餘程式碼
  • 許多的CSS其實是無用的,浪費了頻寬
  • 把樣式檔案切分到許多檔案中,更加瑣碎

2.2. 方式二:Stateful Theming

Styling a UI based on a state or condition.

該方式可以實現基於條件選擇不同的主題皮膚,並允許使用者在客戶端隨時切換主題。非常適合需要客戶端樣式切換功能,或者需要對站點某一部分(區域)進行獨立樣式設定的場景。

具體實現

還是類似上一節中 Tab 的這個例子,我們可以將 Tab 部分的 (S)CSS 改為如下形式:

.tab {
    background-color: gray;
    
    .t-red & {
        background-color: red;
    }
    
    .t-blue & {
        background-color: blue;
    }
}
複製程式碼

這裡我們把.t-red.t-blue稱為 Tab 元素的上下文環境(context)。Tab 元素會根據 context 的不同展示出不同的樣式。

最後我們給body元素加上這個開關

<body class="t-red">
    <ul class="tabs">...</ul>
</body>
複製程式碼

此時 Tab 的顏色為紅色。

當我們將t-red改為t-blue時,Tab 就變為了藍色主題。

進一步的,我們可以建立一些 (S)CSS 的 util class(工具類)來專門控制一些 CSS 屬性,幫助我們更好地控制主題。例如我們使用如下的.u-color-current類來控制不同主題下的字型顏色

.u-color-current {
    .t-red & {
        color: red;
    }
    
    .t-blue & {
        color: blue;
    }
}
複製程式碼

這樣,當我們在不同主題上下文環境下使用.u-color-current時,就可以控制元素展示出不同主題的字型顏色

<body class="t-red">
    <h1 class="page-title u-color-current">...</h1>
</body>
複製程式碼

上面這段程式碼會控制<h1>元素字型顏色為紅色主題時的顏色。

優點

  • 將許多主題放在了同一處程式碼中
  • 非常適合主題切換的功能
  • 非常適合站點區域性的主題化
  • 可以實現將主題應用於所有元素

缺點

  • 有時有點也是缺點,將許多主題混雜在了同一塊程式碼中
  • 可能會存在冗餘

2.3. 方式三:Config Theming

Invoking a theme based on settings.

這種方式其實是在開發側來實現主題樣式的區分與切換的。基於不同的配置,配合一些開發的自動化工具,我們可以在開發時期根據配置檔案,編譯生成不同主題的 CSS 檔案。

它一般會結合使用一些 CSS 前處理器,可以對不同的 UI 元素進行主題分離,並且向客戶端直接提供主題樣式下最終的 CSS。

具體實現

我們還是以 Sass 為例:

首先會有一份 Sass 的配置檔案,例如settings.config.scss,在這份配置中定義當前的主題值以及一些其他變數

$theme: red;
複製程式碼

然後對於一個 Tab 元件,我們這麼來寫它的 Sass 檔案

.tab {
    margin: 0;
    padding: 0;
    
    @if ($theme == red) {
        background-color: red;
    } @else {
        background-color: gray;
    }
}
複製程式碼

這時,我們在其之前引入相應的配置檔案後

@import "settings.config";
@import "components.tabs";
複製程式碼

Tab 元件就會呈現出紅色主題。

當然,我們也可以把我們的settings.config.scss做的更健壯與易擴充套件一些

$config: (
    theme: red,
    env: dev,
)

// 從$config中獲取相應的配置變數
@function config($key) {
    @return map-get($config, $key);
}
複製程式碼

與之前相比,這時候使用起來只需要進行一些小的修改,將直接使用theme變數改為呼叫config方法

.tab {
    margin: 0;
    padding: 0;
    
    @if (config(theme) == red) {
        background-color: red;
    } @else {
        background-color: gray;
    }
}
複製程式碼

優點

  • 訪問網站時,只會傳輸所需的 CSS,節省頻寬
  • 將主題的控制位置放在了一個地方(例如上例中的settings.config.scss檔案)
  • 可以實現將主題應用於所有元素

缺點

  • 在 Sass 中會有非常多邏輯程式碼
  • 只支援有限數量的主題
  • 主題相關的資訊會遍佈程式碼庫中
  • 新增一個新主題會非常費勁

2.4. 方式四:Theme Palettes

Holding entire themes in a palette file.

這種方式有些類似於我們繪圖時,預設了一個調色盤(palette),然後使用的顏色都從其中取出一樣。

在實現主題功能時,我們也會有一個類似的“調色盤”,其中定義了主題所需要的各種屬性值,之後再將這些資訊注入到專案中。

當你經常需要為客戶端提供完全的定製化主題,並且經常希望更新或新增主題時,這種模式會是一個不錯的選擇。

具體實現

在方式三中,我們在一個獨立的配置檔案中設定了一些“環境”變數,來標示當前所處的主題。而在方式四中,我們會更進一步,抽取出一個專門的 palette 檔案,用於存放不同主題的變數資訊。

例如,現在我們有一個settings.palette.red.scss檔案

$color: red;
$color-tabs-background: $color-red;
複製程式碼

然後我們的components.tabs.scss檔案內容如下

.tabs {
    margin: 0;
    padding: 0;
    backgroung-color: $color-tabs-background;
}
複製程式碼

這時候,我們只需要引入這兩個檔案即可

@import "settings.palette.red";
@import "components.tabs";
複製程式碼

可以看到,components.tabs.scss中並沒有關於主題的邏輯判斷,我們只需要專注於編輯樣式,剩下就是選擇所需的主題調色盤(palette)即可。

優點

  • 編譯出來的樣式程式碼無冗餘
  • 非常適合做一些定製化主題,例如一個公司採購了你們的系統,你可以很方便實現一個該公司的主題
  • 可以從一個檔案中完全重製出你需要的主題樣式

缺點

  • 由於主要通過設定不同變數,所以程式碼確定後,能實現的修改範圍會是有限的

2.5. 方式五:使用者定製化 User Customisation

Letting users style their own UIs.

這種模式一般會提供一個個性化配置與管理介面,讓使用者能自己定義頁面的展示樣式。

“使用者定製化”在社交媒體產品、SaaS 平臺或者是 Brandable Software 中最為常見。

具體實現

要實現定製化,可以結合方式二中提到的 util class。

首先,頁面中支援自定義的元素會被預先新增 util class,例如 Tab 元素中的u-user-color-background

<ul class="tabs u-user-color-background">...</ul>
複製程式碼

此時,u-user-color-background還並未定義任何樣式。而當使用者輸入了一個背景色時,我們會建立一個<style>標籤,並將 hex 值注入其中

<style id="my-custom">
    .u-user-color-background {
        background-color: #00ffff;
    }
</style>
複製程式碼

這時使用者就得到了一個紅色的 Tab。

Twitter 就是使用這種方式來實現使用者定製化的介面樣式的:

優點

  • 不需要開發人員的輸入資訊(是使用者定義的)
  • 允許使用者擁有自己“獨一無二”的站點
  • 非常實用

缺點

  • 不需要開發人員的輸入資訊也意味著你需要處理更多的“不可控”情況
  • 會有許多的冗餘
  • 會浪費 CSS 的頻寬
  • 失去部分 CSS 的瀏覽器快取能力

3. 如何選擇方案?

最後來聊聊方案的選擇。

在第二部分我們已經瞭解了五種實現方式(或者說4½種方法,因為第五種其實更偏個性化定製一些),那麼面對產品需求,我們應該如何選擇呢?

這裡有一個不是非常嚴謹的方式可以參考。你可以通過嘗試問自己下面這幾個問題來做出決定:

  • 是你還是使用者誰來確定樣式? 使用者:選擇【方式五】User Customisation

  • 主題是否會在客戶端中被切換? 是:選擇【方式二】Stateful Theming 或【方式五】User Customisation

  • 是否有主題能讓使用者切換? 是:選擇【方式二】Stateful Theming

  • 你是希望網站的某些部分需要有不同麼? 是:選擇【方式二】Stateful Theming

  • 是否有預設的主題讓客戶端來選擇? 是:選擇【方式三】Config Theming

  • 是否是類似“貼牌”這類場景? 是:選擇【方式一】Theme Layer 或【方式四】Theme Palettes

相關文章