組件庫自定義主題換膚實現(xiàn)方案

概述

douluo-ui 組件庫 是基于 element-ui 實現(xiàn)的肄鸽,那么實現(xiàn)換膚分兩步;一是 element-ui 的換膚方案;二是 douluo-ui組件庫 的換膚方案

element-ui 的換膚

方案一:在線生成

訪問 Element 在線主題生成工具痘括,選擇自己所需的顏色谬返,下載主題壓縮包,解壓到項目系統(tǒng)中,按如下方式引入系統(tǒng)

import Vue from 'vue'
import Element from 'element-ui'
import '../theme/index.css'

Vue.use(Element)
  • 優(yōu)點:替換方便
  • 缺點:只能替換一種主題樣式

方案二:直接修改 SCSS 變量

Element 的 theme-chalk 使用 SCSS 編寫,如果項目也使用了 SCSS旗们,那么可以直接在項目中改變 Element 的樣式變量。新建一個樣式文件构灸,例如 element-variables.scss上渴,寫入以下內(nèi)容:

/* 改變主題色變量 */
$--color-primary: teal;

/* 改變 icon 字體路徑變量,必需 */
$--font-path: '~element-gui/lib/theme-chalk/fonts';

@import "~element-gui/packages/theme-chalk/src/index";

之后冻押,在項目的入口文件 src/main.js 中驰贷,直接引入以上樣式文件即可(無需引入 Element 編譯好的 CSS 文件盛嘿,因為已經(jīng) import 了):

import Vue from 'vue'
import Element from 'element-ui'
import './element-variables.scss'

Vue.use(Element)

常用默認(rèn)顏色變量:

$--color-primary: #1890FF;
$--color-success: #57B21C;
$--color-warning: #FC9306;
$--color-danger: #E82F2F;
$--color-info: #999999;

$--color-text-primary: #333333;
$--color-text-empty: #333333;
$--color-text-disabled: #666666;
$--color-text-readonly: #666666;
$--color-text-placeholder: #999999;

$--border-color-base: #D2D2D2;
$--border-color-light: #999999;

$--icon-color: #666666;

$--background-color-base: #EFEFEF;
$--background-color-dark: #E5E5E5;

注意:覆蓋字體路徑變量是必需的洛巢,而且這種方案有個問題,調(diào)試會重復(fù)加載多份樣式次兆,聽說上生產(chǎn)環(huán)境會少點稿茉,原因可能 定義了 common.scss 在多個組件 scss 前面引入

方案實現(xiàn)

  • 優(yōu)點:靈活,可以自定義替換主題和常用的顏色變量等
  • 缺點:只能實現(xiàn)一種換膚

方案三:使用Element的命令行主題工具

由于 element-ui 的樣式單獨維護芥炭,官方將它抽象出來做成命令行工具使用漓库,實現(xiàn)換膚分為5步

1. 安裝工具

a) 首先安裝主題生成工具,可以全局安裝或者安裝在當(dāng)前項目下园蝠,推薦安裝在項目里渺蒿,方便別人 clone 項目時能直接安裝依賴并啟動,這里以全局安裝做演示彪薛。

npm i element-theme -g

b) 安裝白堊主題茂装,可以從 npm 安裝或者從 GitHub 拉取最新代碼。

# 從 npm
npm i element-theme-chalk -D

# 從 GitHub
npm i https://github.com/ElementUI/theme-chalk -D

2. 初始化變量文件

主題生成工具安裝成功后善延,全局安裝可以在命令行里通過 et 調(diào)用工具少态,如果安裝在當(dāng)前目錄下,需要通過 node_modules/.bin/et 訪問到命令易遣。執(zhí)行 -i 初始化變量文件彼妻。默認(rèn)輸出到 element-variables.scss ,當(dāng)然你可以傳參數(shù)指定文件輸出目錄豆茫。

et -i [可以自定義變量文件]

> ? Generator variables file

如果使用默認(rèn)配置侨歉,執(zhí)行后當(dāng)前目錄會有一個 element-variables.scss 文件。內(nèi)部包含了主題所用到的所有變量揩魂,它們使用 SCSS 的格式定義为肮。大致結(jié)構(gòu)如下:

$--color-primary: #409EFF !default;
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */
$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */
$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */
$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */
$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */
$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */

$--color-success: #67c23a !default;
$--color-warning: #e6a23c !default;
$--color-danger: #f56c6c !default;
$--color-info: #909399 !default;

...

3. 修改變量

直接編輯element-variables.scss文件,例如修改主題色為紅色肤京。

$--color-primary: red;

4. 編譯主題

保存文件后颊艳,到命令行里執(zhí)行et編譯主題茅特,如果你想啟用 watch 模式,實時編譯主題棋枕,增加 -w 參數(shù)白修;如果你在初始化時指定了自定義變量文件,則需要增加 -c 參數(shù)重斑,并帶上你的變量文件名

et

> ? build theme font
> ? build element theme

5. 引入自定義主題

默認(rèn)情況下編譯的主題目錄是放在 ./theme 下兵睛,你可以通過 -o 參數(shù)指定打包目錄。像引入默認(rèn)主題一樣窥浪,在代碼里直接引用theme/index.css 文件即可祖很。

import '../theme/index.css'
import ElementUI from 'element-ui'
import Vue from 'vue'

Vue.use(ElementUI)

element-theme 官方換膚方案參考這里

這種換膚方案缺點:只能使用一種主題,安裝比較麻煩漾脂,容易踩坑安裝失敗假颇,不推薦

自定義主題色【推薦】

由于項目會員等級有 4 種主題(未來可能會新增),上面的實現(xiàn)方案有局限性骨稿,如果實現(xiàn)的話要提前準(zhǔn)備 4 種主題笨鸡,這樣做的結(jié)果會增加維護成本,而且拓展性不好坦冠,增加一種新的主題要重新生成一份新的樣式形耗,打包體積也會變大

參考 vue-element-admin 更換主題,通過自定義換膚的方案實現(xiàn)辙浑,這種方式比較靈活激涤,可以自定義任意一種主題顏色,無需準(zhǔn)備多套主題判呕,可以自由動態(tài)換膚倦踢;缺點是自定義不夠,只支持基礎(chǔ)顏色的切換佛玄。

原理

element-ui 2.0 版本之后所有的樣式都是基于 SCSS 編寫的硼一,所有的顏色都是基于幾個基礎(chǔ)顏色變量來設(shè)置的,所以就不難實現(xiàn)動態(tài)換膚了梦抢,只要找到那幾個顏色變量修改它就可以了般贼。

Element官方實現(xiàn)了一個demo:在線主題生成工具。作者在 issue 中回復(fù)了他的方案:

  1. 先把默認(rèn)主題文件中涉及到顏色的 CSS 值替換成關(guān)鍵詞:源碼
  2. 根據(jù)用戶選擇的主題色生成一系列對應(yīng)的顏色值:源碼
  3. 把關(guān)鍵詞再換回剛剛生成的相應(yīng)的顏色值:源碼
  4. 直接在頁面上加 style 標(biāo)簽奥吩,把生成的樣式填進(jìn)去源碼

根據(jù)以上方案哼蛆,簡單說明一下實現(xiàn)原理:

  • 設(shè)計對外暴露拓展配置,包括 element-ui 的版本號 或 element-ui 樣式鏈接 霞赫、新的主題顏色腮介、舊的主題顏色、插入 dom 的位置等
  • 根據(jù)新端衰、舊主題顏色叠洗,轉(zhuǎn)化為 16 進(jìn)制的紅甘改、綠、藍(lán)顏色值灭抑,生成 10%, 20%, ……, 90% 混合顏色十艾,得到兩個顏色數(shù)組, 例如 themeClusteroriginalCluster
  • 請求 element-ui 樣式文件腾节,保存到內(nèi)存中忘嫉,通過正則匹配,將 themeCluster案腺, originalCluster 兩個數(shù)組替換樣式文件
  • 最后將替換的新樣式插入到 dom 上庆冕,覆蓋 element-ui 原來的樣式

實現(xiàn)新功能

  1. 創(chuàng)建 update-element-theme.js 文件,定義替換和加載樣式方法
// 樣式資源包
let chalk = ''
// 默認(rèn)主題
let defaultTheme = '#409EFF'

export const tintColor = (color, tint) => {
  color = color.replace('#', '')
  let red = parseInt(color.slice(0, 2), 16)
  let green = parseInt(color.slice(2, 4), 16)
  let blue = parseInt(color.slice(4, 6), 16)

  if (tint === 0) {
    // when primary color is in its rgb space
    return [red, green, blue].join(',')
  } else {
    red += Math.round(tint * (255 - red))
    green += Math.round(tint * (255 - green))
    blue += Math.round(tint * (255 - blue))

    red = red.toString(16)
    green = green.toString(16)
    blue = blue.toString(16)

    return `#${red}${green}${blue}`
  }
}

export const shadeColor = (color, shade) => {
  let red = parseInt(color.slice(0, 2), 16)
  let green = parseInt(color.slice(2, 4), 16)
  let blue = parseInt(color.slice(4, 6), 16)

  red = Math.round((1 - shade) * red)
  green = Math.round((1 - shade) * green)
  blue = Math.round((1 - shade) * blue)

  red = red.toString(16)
  green = green.toString(16)
  blue = blue.toString(16)

  return `#${red}${green}${blue}`
}

const getThemeCluster = function (theme) {
  const clusters = [theme]
  for (let i = 0; i <= 9; i++) {
    clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
  }
  clusters.push(shadeColor(theme, 0.1))
  return clusters
}

const getCSSString = function (url) {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest()
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4 && xhr.status === 200) {
        chalk = xhr.responseText.replace(/@font-face{[^}]+}/, '')
        resolve()
      }
    }
    xhr.onerror = (err) => {
      console.error('樣式下載失敗', err)
    }
    xhr.open('GET', url)
    xhr.send()
  })
}

const updateStyle = function (style, oldCluster, newCluster) {
  let newStyle = style
  oldCluster.forEach((color, index) => {
    newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
  })
  return newStyle
}

export const updateElementTheme = async function (options = {}) {
  const {
    version = '2.15.8',
    oldTheme,
    primaryColor,
    appendDom,
    insertBefore,
    cssUrl,
    chalkStyle = 'chalk-style'
  } = options
  if (typeof primaryColor !== 'string') return

  const themeCluster = getThemeCluster(primaryColor.replace('#', ''))
  const originalCluster = getThemeCluster((oldTheme || defaultTheme).replace('#', ''))

  const chalkHandler = (id) => {
    const newStyle = updateStyle(chalk, originalCluster, themeCluster)
    // 覆蓋原來的樣式
    chalk = newStyle

    let styleTag = document.querySelector(id)
    if (!styleTag) {
      styleTag = document.createElement('style')
      styleTag.setAttribute('id', id)
      if (appendDom) {
        if (insertBefore) {
          appendDom.parentNode.insertBefore(styleTag, appendDom.nextSibling)
        } else {
          appendDom.appendChild(styleTag)
        }
      } else {
        document.head.appendChild(styleTag)
      }
    }
    styleTag.innerText = newStyle
  }

  if (!chalk) {
    const url = cssUrl || `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
    await getCSSString(url)
  }

  chalkHandler(chalkStyle)
  defaultTheme = primaryColor
}

  1. 在入口文件引入組件劈榨,調(diào)用 updateElementTheme 方法
import { updateElementTheme } from 'douluo-ui'
updateElementTheme({
  oldTheme,
  primaryColor
})

注意:為了避免頁面出現(xiàn)閃爍(原因樣式是異步加載访递,會先渲染 element 主題顏色,再渲染自定義顏色)鞋既,可以在入口文件封裝成方法力九,使用 async/await 初始化變量文件


import DouluoUI from 'douluo-ui'

const initApp = async function () {

  await DouluoUI.updateElementTheme({
    oldTheme,
    primaryColor
  })
  new Vue({
    router,
    render: (h) => h(App)
  }).$mount('#app');
}

initApp()
  1. 異常處理耍铜,如果 css 樣式加載失敗邑闺,會導(dǎo)致頁面加載失敗,打不開棕兼,需要捕獲異常處理
const initApp = async () => {
  try {
    await DouluoUI.updateElementTheme({
      oldTheme,
      primaryColor
    })

    new Vue({
      el: '#app',
      router,
      render: (h) => h(App)
    }).$mount()
  } catch (error) {
    new Vue({
      el: '#app',
      router,
      render: (h) => h(App)
    }).$mount()
    console.error('主題更新失敗')
  }
}

因此陡舅,樣式最好放在公司內(nèi)部的 cdn 上,保證樣式資源穩(wěn)定安全伴挚,同時最好做一下降級處理方案兼容加載失敗的情況

組件庫的換膚

組件庫參考 element-plus 使用 SCSS 變量和 css 自定義屬性靶衍,所以實現(xiàn)換膚比較簡單,參考
element-plus CSS 變量設(shè)置茎芋,使用 document.documentElement.style.setProperty 設(shè)置樣式颅眶,覆蓋 root 的樣式

export const updateUITheme = async function (options) {
  const {
    oldTheme = '#409EFF',
    primaryColor,
  } = options
  if (!primaryColor) return

  await updateElementTheme({
    oldTheme,
    primaryColor: primaryColor
  })

  const el = document.documentElement
  el.style.setProperty(`--${cssNamespace}-color-primary`, primaryColor)
  for (let i = 1; i <= 9; i++) {
    const color = tintColor(primaryColor, Number((0.1 * i).toFixed(2)))
    el.style.setProperty(`--${cssNamespace}-color-primary-light-${i}`, color)
  }
}

兼容 button 兩種主題色

新的 UI 規(guī)范 button 的主要按鈕由兩種顏色組成,不是默認(rèn)的白色田弥,如下

為了實現(xiàn)這種交換涛酗,在 button-variantprimary 類型做判斷,定義一份新的數(shù)據(jù)結(jié)構(gòu)偷厦,根據(jù) button 組件的 css 自定義屬性實現(xiàn)換膚商叹,文件定義在 update-ui-theme.js

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市只泼,隨后出現(xiàn)的幾起案子剖笙,更是在濱河造成了極大的恐慌,老刑警劉巖请唱,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弥咪,死亡現(xiàn)場離奇詭異过蹂,居然都是意外死亡,警方通過查閱死者的電腦和手機聚至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門榴啸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晚岭,你說我怎么就攤上這事鸥印。” “怎么了坦报?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵库说,是天一觀的道長。 經(jīng)常有香客問我片择,道長潜的,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任字管,我火速辦了婚禮啰挪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嘲叔。我一直安慰自己亡呵,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布硫戈。 她就那樣靜靜地躺著锰什,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丁逝。 梳的紋絲不亂的頭發(fā)上汁胆,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音霜幼,去河邊找鬼嫩码。 笑死,一個胖子當(dāng)著我的面吹牛罪既,可吹牛的內(nèi)容都是我干的铸题。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼萝衩,長吁一口氣:“原來是場噩夢啊……” “哼回挽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起猩谊,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤千劈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后牌捷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墙牌,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡涡驮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喜滨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捉捅。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖虽风,靈堂內(nèi)的尸體忽然破棺而出棒口,到底是詐尸還是另有隱情,我是刑警寧澤辜膝,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布无牵,位于F島的核電站,受9級特大地震影響厂抖,放射性物質(zhì)發(fā)生泄漏茎毁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一忱辅、第九天 我趴在偏房一處隱蔽的房頂上張望七蜘。 院中可真熱鬧,春花似錦墙懂、人聲如沸橡卤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒜魄。三九已至扔亥,卻和暖如春场躯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旅挤。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工踢关, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粘茄。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓签舞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柒瓣。 傳聞我的和親對象是個殘疾皇子儒搭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容