概述
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ù)了他的方案:
- 先把默認(rèn)主題文件中涉及到顏色的 CSS 值替換成關(guān)鍵詞:源碼
- 根據(jù)用戶選擇的主題色生成一系列對應(yīng)的顏色值:源碼
- 把關(guān)鍵詞再換回剛剛生成的相應(yīng)的顏色值:源碼
- 直接在頁面上加 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ù)組, 例如themeCluster
,originalCluster
- 請求
element-ui
樣式文件腾节,保存到內(nèi)存中忘嫉,通過正則匹配,將themeCluster
案腺,originalCluster
兩個數(shù)組替換樣式文件 - 最后將替換的新樣式插入到
dom
上庆冕,覆蓋 element-ui 原來的樣式
實現(xiàn)新功能
- 創(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
}
- 在入口文件引入組件劈榨,調(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()
- 異常處理耍铜,如果 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-variant 對 primary
類型做判斷,定義一份新的數(shù)據(jù)結(jié)構(gòu)偷厦,根據(jù) button 組件的 css 自定義屬性實現(xiàn)換膚商叹,文件定義在 update-ui-theme.js