前端組件庫自定義主題切換探索-01-方案借鑒與思路參考

探索原因背景

首先自然是項目有需求,這是必須去做的原因
其次,是我們項目沒有直接使用市面上現成的基于element-ui或者ant-design的第三方UI框架,比如avue,而是有著自己的UI組件庫
第三,我們的組件庫基于ant-design-vue,而ant-design-vue并沒有很好的支持主題動態切換(主題總體模式僅支持深色和淺色,其他顏色只支持主色切換,不支持其他顏色和屬性動態定制)
第四,我們還在使用Vue2,這也許是最痛苦的,因為vue3的UI庫比如ant-design-vue,直接支持動態主題

期望目標和效果

1、用戶可以自定義主題色
2、用戶可以切換內置好的主題
3、用戶可以同時自定義配置主題其他顏色細節(包括除主題以外的其他顏色,比如警告色,錯誤顏色等)
4、用戶可以同時自定義非顏色的細節,比如輸入框圓角,placeholder顏色,組件尺寸等
5、不同的組件可以單獨定制
6、同時支持ant-design-vue和自研組件庫的主題定制

方案和思路探索

在開始之前,我們先研究下現有的方案,參照下別人的思路

可用方案參考

1、樣式覆蓋
通過切換 css 選擇器的方式實現主題樣式的切換
2、多套css切換
即寫多套css,覆蓋原有的,并且根據條件切換css
比如 bladex 的主題皮膚切換就是這樣做的,不同的皮膚主題,對應不同的css

image.png

image.png

這里iview主題,對應view.scss,d2主題,對應d2.scss

3、css變量實現

  • 變量聲明
    :root {
     --primary-color: #878ef6;
     --warning-color: #98efd7;
    }
    div{
       --primary-color: #878ef6;
     --warning-color: #98efd7;
    }
    

其中 :root表示全局變量,而使用其他的比如在div里面聲明的,只對div標簽有效

  • 變量的使用
    使用時用var()函數獲取
.button-primary {
 color: var(--primary-color);
}
.button-warning {
 color: var(--warning-color, "#e98321");
}

其中,var函數的第二個參數表示默認值

  • 使用javascript操作
// 設置變量
document.body.style.setProperty('--primary-color', '#878ef6');

// 讀取變量
document.body.style.getPropertyValue('--primary-color').trim();
// '#7F583F'

// 刪除變量
document.body.style.removeProperty('--primary-color');

  • 兼容性處理
    css變量在低端瀏覽器不被支持,需要使用 css-vars-ponyfill 做兼容處理

方案優缺點

以上方案和方案優缺點參考文章:前端 “一鍵換膚“ 的幾種方案

總體來說,使用變量覆蓋和多套css切換,都不能很自由的滿足主題樣式定制,css變量可以動態的進行樣式設置,是最優方案
a、要切換的目標主題得是我們首先定制好的,比如黑色,白色主題。
b、用戶無法自己設置自己的主題,比如自己制定一個藍色主題
c、css變量能滿足這些需求,理論上我們可以讓用戶自己設置他喜歡的任意部位的顏色樣式

我們面臨的其他問題

如果我們的UI組件庫完全是自己基于vue原生開發的,那選用css變量方案,無疑是最佳方案。
然而,我們的組件庫是基于ant-design-vue 1.x 封裝的,由于一些原因目前還不能使用vue3。這里強調1.x,是因為基于vue3 的 ant-design-vue 3.x已經提供了css變量來實現動態主題定制

ant-design-vue 1.x 的主題變量使用less的變量,


image.png

如上圖,在ant-design-vue 的 style/theme里面定義了default.less,如果我們只是想靜態的改變主題,那重寫或者覆蓋這些變量即可

那能不能動態的改變less的變量顏色,達到類似css變量的效果呢?有,我們來看看別人的參考方案

  • 在線編譯less變量方案
    該方案參考文章:動態改變主題顏色,less變量動態改變
    該方案的實現,參考上述文章,經實踐確實可以達到動態改變Less變量的目的,然而對我們確無法適用。該方案缺點:
    a、由于是動態編譯less,因此我們需要類似如下的結構。在public中新建一個color.less文件,在文件中定義好變量,并且將對應的樣式寫在該less文件中
    image.png

這是無法接受的
第一是項目樣式考慮兼容性等原因,需經編譯器處理后,統一輸出成css
第二是如果一個龐大的項目使用了這種在線編譯Less的結構,在性能上估計會有不少耗損(暫時沒有去實踐和驗證大項目的在線編譯)
第三我們使用的有自己的組件庫以及組件庫的基礎UI庫 ant-design-vue,組件庫使用npm管理,且有基礎組件庫和業務組件庫之分,這種結構不利于組件庫的使用,也不利于樣式的管理(動態編譯時需要去查找color.less的路徑)

b、無法覆蓋ant-design-vue的主題樣式。原因是less作為css預編譯語言使用,在ant-design-vue里面只存在于開發模式下,生產環境打包后的只有css,類似@primary-color這樣的變量也不會存在,已經被轉換為 #D04A02這樣的具體顏色

  • 使用webpack-theme-color-replacer webpack插件動態修改顏色
    首先ant-design-vue-pro UI框架就是使用了這個插件來動態修改顏色,至于其他參考文章,可能你看到的大部分都是關于element-ui的實現,因為這個插件里面直接包含了一個forElementUI工具函數庫

webpack-theme-color-replacer webpack 基本思路就是,webpack構建時,在emit事件(準備寫入dist結果文件時)中,將即將生成的所有css文件的內容中 帶有指定顏色的css規則單獨提取出來,再合并為一個theme-colors.css輸出文件。然后在切換主題色時,下載這個文件,并替換為需要的顏色,應用到頁面上

所以我們先直接使用ant-design-vue-pro的修改主題代碼來測試一下
首先我們在項目根目錄建文件夾config,在文件夾下建文件plugin.config.js,用來注冊插件
plugin.config.js

const ThemeColorReplacer = require("webpack-theme-color-replacer")
const generate = require("@ant-design/colors/lib/generate").default

const getAntdSerials = (color) => {
  // 淡化(即less的tint)
  const lightens = new Array(9).fill().map((t, i) => {
    return ThemeColorReplacer.varyColor.lighten(color, i / 10)
  })
  console.log("lightens", lightens)
  const colorPalettes = generate(color)
  console.log("colorPalettes", colorPalettes)
  const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace("#", "")).join(",")
  // console.log("rgb", rgb)
  const matchColors = lightens.concat(colorPalettes).concat(rgb)
  // console.log("matchColors", matchColors)
  return matchColors
}

const themePluginOption = {
  matchColors: [
      ...getAntdSerials("#1890ff"), // 主色系列 1890ff
  ], // 主色系列 1890ff
}

const createThemeColorReplacerPlugin = () => new ThemeColorReplacer(themePluginOption)

module.exports = createThemeColorReplacerPlugin

這里特別要注意的是getAntdSerials("#1890ff"),getAntdSerials 函數傳遞的顏色參數,一定要是ant-design-vue的色系顏色,否則webpack-theme-color-replacer webpack 無法提取出正確的css字符。這里傳遞的#1890ff 正是ant-design-vue的默認主題藍色

然后在vue.config.js中注冊插件
vue.config.js

const createThemeColorReplacerPlugin = require("./config/plugin.config")
module.exports = {
  configureWebpack: config => {
    config.plugins.push(createThemeColorReplacerPlugin())
  },
  css: {
    loaderOptions: {
      less: {
        javascriptEnabled: true
      }
    }
  },
}

新建測試頁面theme-example,寫幾行測試效果的代碼

<template>
      <div>
        <a-button type="primary">主色</a-button>
        <a-button type="danger">警告色</a-button>
        <a-button @click="changeTheme">點擊切換</a-button>
      </div>
</template>
<script lang="ts">
import { Component, Vue, Ref } from "vue-property-decorator"
import { updateTheme } from "@ant-design-vue/pro-layout"

@Component()
export default class ThemeExample extends Vue {
  isDefault= true // 是否默認
  /**
   * 改變主題色
   */
  changeTheme() {
    this.isDefault= !this.isDefault
    updateTheme(this.isDefault?  "#1890ff" : "#cf56d7")
  }
}
</script>

然后隨便找個路由頁面引入上述組件或者直接把他做成路由組件測試效果吧

[video(video-z7hFSmxg-1673186996605)(type-csdn)(url-https://live.csdn.net/v/embed/268750)(image-https://video-community.csdnimg.cn/vod-84deb4/0968a0708f5471edbfdf0764a0ec0102/snapshots/58c240f0130f4e5aa42669f280d26105-00001.jpg?auth_key=4826782667-0-0-a0b017aeb824a925545af9b494d65564)(title-20230108_205657)]

確實可以成功切換,但是只能切換主題色,如果我們想切換其他顏色,比如危險顏色,怎么做呢?
可以將 getAntdSerials 函數的顏色參數換成 ant-design-vue的危險色 #F5222D,即 getAntdSerials("#F5222D"),然后我們重啟下項目,記得要重啟,因為vue.config.js要重新注冊插件,重啟后看效果

[video(video-01qtjoiC-1673186985298)(type-csdn)(url-https://live.csdn.net/v/embed/268751)(image-https://video-community.csdnimg.cn/vod-84deb4/42e4be408f5671edbfe26723b78e0102/snapshots/1f6a1bdd772b4982beaff5237fd4f741-00001.jpg?auth_key=4826783623-0-0-2c7d84dac6ca370e08da569abbf25e47)(title-20230108_210855)]

可以看到這次切換的顏色是危險色,即a-button type="danger" 時的顏色,而默認的主題色type="primary"并沒有被切換

那該方案是否能滿足我們的需求和期望呢?
答案是否定的,對比下文章開頭,我們的需求和期望,只有第一點可以滿足,就是自定義主題色,危險色雖然也能定制,但是我們卻無法同時分別定制主題色和危險色。

那我們不妨來研究下 webpack-theme-color-replacer webpack 的實現原理,看看是否可以改進后達到我們的目標和需求。內容比較長,我們在下一篇文章 《前端組件庫自定義主題切換探索-02--webpack-theme-color-replacer webpack 的實現邏輯和原理》 來研究下

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容