UI 框架的主題色一般怎麼實現的?

之前用過material-ui,裡面可以變化主題色,我們現在要根據傳入的不同參數,做主題色。源文件的是sass。

解決思路:

1、通過服務端編譯,node-sass或者ruby,移動端感覺性能不好

2、本地編譯好幾套css模板,不是有限級,不夠擴展

3、定義主題的class名,在用到地方加入class名,再通過js動態改變class名里的顏色,我暫時是這樣做的,但是,有一些地方會用到:before,就要特殊處理,感覺也不友好


要在客戶端動態切換主題顏色,要做的無非是兩點:

  1. 定義所有元素的顏色樣式和主題色的映射關係;
  2. 在客戶端主題色切換時觸發樣式切換。

建立映射

以下(總體上)動態程度遞增,可用程度(兼容性)遞減:

  • 預處理器的色彩函數(編譯時),可以靈活定義映射關係:

    @brand-color: #e10601;
    @brand-color-light: tint(@brand-color, 10%);

    h1 {
    color: @brand-color;
    }

    h2 {
    color: @brand-color-light;
    }

  • currentColor(運行時),只能表達直接使用 color 屬性值:

    html {
    color: #e10601;
    }

    h1 {
    background-color: currentColor;
    color: #fff;
    }

  • 用 CSS 變數(運行時),可以使用多個變數,不依賴 color,但需要枚舉值:

    :root {
    --brand-color: #e10601;
    --brand-color-light: #e41f1a;
    }

    h1 {
    color: var(--brand-color);
    }

    h2 {
    color: var(--brand-color-light);
    }

  • color-mod() 函數(運行時),可以像預處理器一樣靈活定義映射關係,但是這還只存在於CSS Color Module Level 4這份草案中,並沒有瀏覽器實現。結合 CSS 變數的話,可以寫成:

    :root {
    --brand-color: #e10601;
    --brand-color-light: color-mod(var(--brand-color) tint(10%));
    }

    h1 {
    color: var(--brand-color);
    }

    h2 {
    color: var(--brand-color-light);
    }

    這樣才基本上在瀏覽器端實現了預處理器的相關功能。這裡有一個在線演示可以看看語法(注意這裡用的是 color() 函數而非 color-mod(),是因為 color-mod() 是草案中後來修改過的名字)。

切換樣式

很顯然,上面四中方法中只有預處理器、CSS 變數是有可能實際使用的。那麼:

  • 如果用預處理器,服務端編譯服務肯定是可行的,只是速度可能不會太快。Element UI 2.0 文檔右上角加了更換主題色的功能,應該就是通過服務端編譯實現的。Less.js 支持在前端動態編譯 Less 樣式,雖然也是一個選項,但是性能也不怎麼樣,而且你需要在前端引入 Less.js 的整個運行環境。這些方法本質上都是在前端動態生成 & 元素來修改樣式。
  • 如果你的目標瀏覽器支持 CSS 變數,那麼你只要通過 CSSOM 操作樣式來修改變數值,就可以搞定了。color-mod() 函數的功能你可以在 JS 中實現。

    import { tint } from "./utils/color"

    function updateBrandColor (color) {
    let root = document.documentElement

    root.style.setProperty("--brand-color", color)
    root.style.setProperty("--brand-color-light", tint(color, 0.1))
    }

    updateBrandColor("#2319dc")

實際一些

又要快(性能高、不依賴網路),又想兼容更多瀏覽器,還想靈活定義映射、易於切換?暫時應該是做不到的。但是天無絕人之路,硬要做么,砍需求、降低靈活性,還是可以搞搞的。

問題描述中的第二種方法,其實就是把靈活性砍到很低了。

我感覺更可行的方案,是採取類似前端 Less 編譯的方式,自己寫一個更輕量級的運行時環境。但是我們需要的功能只有色彩的動態切換,所以為了減少運行環境的代碼,我們可以不提供語法解析、求值等功能,僅僅做做正則匹配和替換。為此,我們還要對預處理語言的功能做一定的限制,比如,僅使用枚舉的顏色,不支持色彩函數。簡單思路是這樣的:

假設有如下的 Less 文件:

@brand-color: #e10601;
@brand-color-light: #e41f1a;

h1 {
color: @brand-color;

small {
color: @brand-color-light;
font-weight: normal;
}
}

h2 {
color: @brand-color-light;
}

正常編譯結果為:

h1 {
color: #e10601;
}
h1 small {
color: #e41f1a;
font-weight: normal;
}
h2 {
color: #e41f1a;
}

這個結果會給前端作為基礎樣式使用。

讓我們再編譯一次,這次加入編譯選項:

modifyVars: {
"brand-color": "--brand-color--",
"brand-color-light": "--brand-color-light--",
}

這裡我們用 -- 標記變數以方便我們替換,這樣並不會報錯,渲染結果會成為:

h1 {
color: ---brand-color--;
}
h1 small {
color: --brand-color-light--;
font-weight: normal;
}
h2 {
color: --brand-color-light--;
}

這樣我們就得到了一個簡單的「CSS 模板」,這個模板作為字元串被前端的 JS 邏輯引入,隨時待命。

// 可以非同步載入這個模塊,保持首屏依然最小
import style from "./style.tpl"
import { appendStyleElement } from "./utils/dom"
import { tint } from "./utils/color"

function updateBrandColor (color) {
// 僅作為示例,枚舉變數替換邏輯
// 變數間的關係從 Less 中移入這裡定義
// 更好的方式可以在外部進行定義後導入這裡動態調用
appendStyleElement(
style
.replace("--brand-color--", color)
.replace("--brand-color-light--", tint(color, 0.1))
)
}

updateBrandColor("#2319dc")

這樣就用同一份 Less 代碼導出了兩份 CSS,一份直接插入 DOM,另一份在動態切換時作為模板。更進一步,我們可以用 Postcss 處理一下「模板」,去除掉不含顏色定義的屬性聲明,讓模板最小化。我們依然保持在樣式文件中建立映射,但將不同顏色的關係定義進行外置,移到客戶端進行計算。使用類似 Less.js 在瀏覽器動態生成 CSS 的方式進行切換,但將代價最小化,保持整體邏輯的簡單、載入時間不會太久。

這是我對解決這個問題的一個簡單思路,希望對你有所幫助。


定義顏色變數啊:

@global-color: #666;
@global-emphasis-color: #333;
@global-muted-color: #999;

@global-link-color: #1e87f0;
@global-link-hover-color: #0f6ecd;

@global-inverse-color: #fff;

參考uikit:

uikit/uikit


一般不都是在最外層加上主題類名,裡面的顏色都用顏色變數么


4.用 js 動態生成 style節點。document.createElement(「style」)。當然 ie 需要做些兼容處理。


佔個坑,有空了來回答。


本地做好映射關係,比如@primary:#000000,js中也維護一份映射表,每次切換主題,下拉編譯後的css,把所有的顏色都替換成定義的key,例如把#000000都替換成 @primary,然後再統一修改把@primary改為新的色值,把新的樣式插到head style中。


最簡單就是全局類名啊,


推薦閱讀:

關於Angular和vue的對話,對前端圈子到底起到什麼作用,能不能推進前端的發展?
技術圈爭論跑去告老闆會讓人不齒嗎?

TAG:CSS | CSS框架 | Vuejs |