隨著嵌入阿里數(shù)據(jù)頁面的需求增多珊佣,出現(xiàn)了一些要求較高的業(yè)務(wù)方蹋宦,希望能指定阿里數(shù)據(jù)的主題色,來和他們的品牌色保持一致咒锻,讓產(chǎn)品有更好的體驗(yàn)冷冗。于是我們開始了動(dòng)態(tài)主題功能的調(diào)研,本來以為是個(gè)比較簡(jiǎn)單的事情惑艇,實(shí)際還是有遇到一些曲折的蒿辙,不過最終效果挺滿意的拇泛,符合我們的需求,同時(shí)很簡(jiǎn)單思灌、很輕量俺叭。
一、現(xiàn)有方案
動(dòng)手之前先調(diào)研了一波可選的方案泰偿,現(xiàn)有方案大致是兩類:一類是編譯構(gòu)建的時(shí)候就有多份預(yù)設(shè)主題熄守,在運(yùn)行時(shí)只做樣式切換,新增一類主題需重新構(gòu)建發(fā)布耗跛;另一類是運(yùn)行時(shí)可指定任意主題裕照,新增一類主題幾乎沒有成本,很方便拓展调塌。
1牍氛、編譯多份預(yù)設(shè)主題
Class切換
編譯出的CSS示例如下,通過給body設(shè)置theme-light
或theme-dark
的class
來切換主題烟阐。借助Less/Sass的能力可以較方便批量處理,但弊端也很明顯紊扬,CSS文件大小會(huì)倍增蜒茄。
// index.css
.theme-light div {
color: #000;
}
.theme-dark div {
color: #fff;
}
構(gòu)建多份CSS
對(duì)于class切換方案的弊端,我們可以通過構(gòu)建多份css文件來解決餐屎,比如我們的less代碼如下:
div {
color: @primary-color;
}
然后構(gòu)建階段指定@primary-color
為不同色值檀葛,來得到多份css產(chǎn)物:
// index-light.css
.div {
color: #000;
}
// index-dark.css
div {
color: #fff;
}
在運(yùn)行期間,通過切換加載index-light.css
和index-dark.css
腹缩,來達(dá)到切換主題的效果屿聋。但這個(gè)方案也不友好,需要侵入構(gòu)建配置藏鹊,然后構(gòu)建耗時(shí)會(huì)增加润讥。
在編譯階段預(yù)置多份主題的方案,實(shí)現(xiàn)還算簡(jiǎn)單盘寡,原理也很好理解楚殿,但都有存在明顯的弊端,且拓展新主題會(huì)有成本竿痰。
2脆粥、運(yùn)行時(shí)指定任意主題
如果要低成本拓展主題,就必須借助運(yùn)行時(shí)的能力了影涉,實(shí)現(xiàn)復(fù)雜度由難到易变隔,大致有下面三種方案:
CSS模板+運(yùn)行時(shí)替換
這類方案需要先準(zhǔn)備一個(gè)css模板文件,然后根據(jù)用戶指定的顏色值注入到模板里去蟹倾,動(dòng)態(tài)產(chǎn)出最終的css文件匣缘,Element-ui便是使用了這個(gè)方案,在切換顏色時(shí),會(huì)把色值作為參數(shù)傳給后端來得到新的css文件:
當(dāng)然這個(gè)工作純前端也可以做孵户,方案還是挺不錯(cuò)的萧朝。
Less變量
這個(gè)方案借助less.js
的運(yùn)行時(shí)能力,來實(shí)現(xiàn)類似上面方案的效果夏哭,css模板在這里成了less文件检柬,運(yùn)行時(shí)替換由less.js
的modifyVars
提供,我們的編碼工作量變少了竖配。使用大概是這樣:
- html主文檔增加項(xiàng)目的less文件和
less.js
<link rel="stylesheet/less" type="text/css" href="/styles.less" />
<script src="https://cdn.jsdelivr.net/npm/less@4.1.1"></script>
- 使用
modifyVars
修改顏色變量
window.less.modifyVars({
'@primary-color': '#0035ff'
})
該方案的弊端不少何址,一是要引入40kb的less.js
運(yùn)行時(shí);二是對(duì)編碼和打包一定侵入进胯,我們需要把所有l(wèi)ess文件加到html中用爪;三是主題切換不會(huì)很流暢,因?yàn)?code>modifyVars后涉及到less文件的重新編譯胁镐。
CSS變量
CSS變量是CSS3標(biāo)準(zhǔn)的新功能偎血,通過它做主題很簡(jiǎn)單,比如下面是我們的樣式:
div {
// 使用--primary-color變量的顏色盯漂,無值則用默認(rèn)的#000
color: var(--primary-color, #000);
}
切換主題時(shí)颇玷,只需通過document
的API設(shè)置新的顏色值即可
document.body.style.setProperty('--primary-color', '#fff')
感覺這個(gè)方案是上述所有方案里最簡(jiǎn)單的,功能強(qiáng)大也很好理解就缆。
此外也有類似類似styled-components的css in js方案帖渠,但對(duì)項(xiàng)目改動(dòng)太大了,且指定antd組件的主題會(huì)很麻煩竭宰,可以直接忽略空郊。
二、我們的選擇
編譯多份預(yù)設(shè)主題類型的方案首先被我們拍死了切揭,我們對(duì)低成本拓展的要求比較高狞甚,否則業(yè)務(wù)方如果換主題色,我們還得跟著發(fā)版...
運(yùn)行時(shí)指定任意主題的方案里廓旬,功能上都能滿足我們的需求入愧,其中CSS變量方案成本最低,且:
- 主流瀏覽器都已支持嗤谚,然后我們產(chǎn)品的用戶99%+都是chrome棺蛛,兼容性可以不考慮;
- 然后我們本身已經(jīng)使用了less巩步,通過把
@primary: #ff6a00
改成@primary: var(--primary-color, #ff6a00)
可以很方便使用旁赊,不需要大量改動(dòng)項(xiàng)目中已有的less文件; - 我們發(fā)現(xiàn)antd也已經(jīng)支持了CSS變量動(dòng)態(tài)指定主題椅野;
完美终畅,所以我們最終選擇了CSS變量方案籍胯。
三、動(dòng)手實(shí)踐
1离福、修改global.less
把寫死的less變量的值杖狼,改成CSS變量的方式,原先的值放入默認(rèn)值
- @primary: #ff6a00;
- @primary5: #ff6a000d;
- @primary15: #ff6a0026;
- @primary75: #ff6a00BF;
+ @primary: var(--primary-color, #ff6a00);
+ @primary5: var(--primary-color-5, #ff6a000d);
+ @primary15: var(--primary-color-15, #ff6a0026);
+ @primary75: var(--primary-color-75, #ff6a00BF);
本地跑一下妖爷,沒啥問題蝶涩,顏色都正常
2、修改CSS變量
我們新增了一個(gè)theme.ts
文件絮识,在入口會(huì)執(zhí)行它的setupTheme
來使用URL參數(shù)上指定的主題色绿聘。
// theme.ts
import { getUrlParams } from '@/utils/utils';
export let primaryColor = '#ff6a00';
export let primaryColor5 = '#ff6a000d';
export let primaryColor15 = '#ff6a0026';
export let primaryColor75 = '#ff6a00BF';
export function setupTheme() {
const params = getUrlParams() as any;
if (params?.primaryColor) {
primaryColor = `#${params?.primaryColor.toLocaleLowerCase()}`;
primaryColor5 = `${primaryColor}0d`;
primaryColor15 = `${primaryColor}25`;
primaryColor75 = `${primaryColor}BF`;
}
document.body.style?.setProperty('--primary-color', primaryColor);
document.body.style?.setProperty('--primary-color-5', primaryColor5);
document.body.style?.setProperty('--primary-color-15', primaryColor15);
document.body.style?.setProperty('--primary-color-75', primaryColor75);
}
本地跑一下,指定primaryColor參數(shù)為其它色值次舌,除了antd系列組件都已經(jīng)生效了
3熄攘、指定antd組件主題
跟著文檔指引,我們升級(jí)了antd
到4.17.1-alpha.1
版本彼念,import
了antd/dist/antd.variable.min.css
挪圾,然后在theme.ts
里新增了ConfigProvider
來設(shè)置主題色,刷新下頁面逐沙,antd系列組件也生效了哲思,完美!打包發(fā)到預(yù)發(fā)感受下...
發(fā)到預(yù)發(fā)后問題來了酱吝,antd只有部分組件主題生效了,有些組件如分頁器沒生效土思,調(diào)試發(fā)現(xiàn)antd樣式有冗余务热,部分組件的CSS變量版樣式被覆蓋了,我們的umi.css
大概是這樣:
// CSS變量版的樣式在前己儒,被后面的覆蓋了崎岂,導(dǎo)致指定的主題色沒生效
.ant-pagination-item-active {
border-color: var(--ant-primary-color);
}
.ant-pagination-item-active {
border-color: #ff6a00;
}
我的第一反應(yīng)是修改下CSS順序,讓CSS變量版的樣式在后闪湾,折騰了一番發(fā)現(xiàn)不行冲甘,發(fā)現(xiàn)webpack打包后的CSS順序不和import順序一致,最后看到這個(gè)issue就決定放棄了途样,webpack成員表示他們不能保證這個(gè)順序江醇。
4、關(guān)閉antd的按需加載
如antd的文檔所寫何暇,antd動(dòng)態(tài)主題需要關(guān)閉按需加載:
注:如果你使用了 babel-plugin-import陶夜,需要將其去除。
看來只能這樣了裆站,從umi文檔得知条辟,@umijs/plugin-antd
會(huì)對(duì)antd做按需引入黔夭,翻了下源碼目前無法配置關(guān)閉,我們于是提了個(gè)PR羽嫡,釘釘私聊了期賢本姥,期賢了解了背景后很快合并了PR并發(fā)了新版本,非常贊杭棵。
接著我們升級(jí)了@umijs/preset-react
婚惫,在.umirc.ts
里新增了disableBabelPluginImport
配置禁用了按需加載,可喜的是打包后的產(chǎn)物竟然還有一些減少颜屠,看來大量使用antd組件時(shí)沒必要開啟按需加載...
antd: {
disableBabelPluginImport: true
}
應(yīng)該穩(wěn)了辰妙,發(fā)到預(yù)發(fā)再感受一下...
分頁器是好了,但按鈕的居然壞了甫窟,一調(diào)試發(fā)現(xiàn)還是因?yàn)槿哂鄻邮矫芑耄珻SS變量樣式被覆蓋導(dǎo)致,最終定位到是@ant-design/pro-layout
引入的
5粗井、指定pro-layout主題
原因是pro-layou
有引用lib|es 目錄下的 less 文件尔破,按antd文檔所說,需要在less中注入@root-entry-name: variable
變量浇衬,在.umirc.ts
中配置如下:
theme: {
'root-entry-name': 'variable'
}
再次發(fā)到預(yù)發(fā)懒构,OK,全妥了耘擂!
注意在theme里配置了'root-entry-name': 'variable'后胆剧,不能再配置'primary-color'的值
四、總結(jié)
可以看到醉冤,基于CSS變量的動(dòng)態(tài)主題方案還是比較簡(jiǎn)單的秩霍,對(duì)于已經(jīng)使用less的項(xiàng)目,接入成本很低蚁阳,方案輕量好拓展铃绒,并且antd也已經(jīng)給了官方支持,進(jìn)一步降低了使用成本螺捐,相信以后會(huì)成為更多人的選擇颠悬。
拓展閱讀:
CSS 變量教程:https://www.ruanyifeng.com/blog/2017/05/css-variables.html
Element-ui換膚方案:https://github.com/ElemeFE/element/issues/3054
聊一聊前端換膚:https://segmentfault.com/a/1190000018593994