線上預覽地址:Dynamic-Antd-Theme-Demo css
// 如何使用
import DynamicAntdTheme from 'dynamic-antd-theme';
render() {
...
<DynamicAntdTheme />
...
}
複製代碼
沒錯,就是這麼簡單,爲了方便你們,我已經弄成了即插即用的組件~歡迎你們star + issue + pr。dynamic-antd-themehtml
這裏是我很想說的,我尚未自負到或者說貪心到有能力去實現一個完美的合理的antd動態換膚方案。好比開源社區裏的antd-theme-webpack-plugin和antd-theme-generator就是很成熟的方案,利用webpack的方式在頁面裏經過引入less.js實現動態換膚,換的很完全。前端
那麼爲何我還要來搞一個所謂的動態換膚呢?理由以下:react
index.html
,做爲nextjs黨的我,就怎麼找也找不到,因此在個人nextjs腳手架項目裏就沒有配置成功。這裏安利一波,Next-Antd-Scaffold目前已經基本開發完成了,我我的也在用這個架子寫項目,有不少小夥伴也在寫項目,應該還能夠,對Nextjs感興趣的同窗能夠加入文章底部的微信羣一塊兒溝通哦~最主要的,你給我一個star,我全力以赴盡我所能解決問題,😄webpack
綜上所述,我的花費了週末的時間,搞出來一個即插即用的antd換膚方案,一鍵安裝直接使用。固然了,既然是這麼簡單,確定也有弊端,畢竟個人主題是最簡單的antd換膚方案而不是最完美的antd換膚方案,詳細的聽我娓娓道來。git
具體的實現過程,我將會從思路,解決方案、細節難點以及項目存在的弊端問題來進行說明。github
這裏我全部的例子都是經過Next-Antd-Scaffold來進行編寫的~web
先來講思路,既然我想標新立異,最簡單的antd的換膚方案,那確定不能有複雜的配置過程以及表述不清的文檔。固然,說實話,在沒深讀源碼以及webpack機制以前,我尚未能力寫出上面兩個的那種水平的插件,哈哈😄。npm
因此,就只能另闢蹊徑了。個人想法就是,能不能在系統運行過程當中,經過類覆蓋的方式來動態修改顏色,由於CSS加載機制是由上至下,同名會覆蓋對應屬性嘛,利用這個特性就來簡單的嘗試一下。數組
以button按鈕爲例,能夠看到,咱們設置的@primaryColor = #524c1a
,類名是.ant-btn-primary
,那麼咱們來進行覆蓋。
// 樣式變換代碼
const styleDom = document.createElement('style');
styleDom.innerHTML = `
.ant-btn-primary {
border-color: #0000ff;
background-color: #0000ff;
}
`;
document.getElementsByTagName('head')[0].appendChild(styleDom);
複製代碼
能夠看到,頁面在未刷新的前提下,按鈕實現了變色。所以,這個思路是可行的,接下來咱們要考慮的就是細節問題了。
經過上述實踐,咱們肯定了思路,這裏就來肯定可行性方案。說實話,類名覆蓋這種問題大部分前端開發應該都能想到,應該也有一部分人在用,畢竟換膚的需求是不少中後臺系統的基本需求,不過即便是類名覆蓋不一樣人也有不一樣的作法,並且類名覆蓋的難點在於 —— 覆蓋基礎顏色還好,若是爲每個僞類元素如:hover :active :focus
等都覆蓋一個合適的color,不只實現困難,並且工做量也很大。我這裏想的就是,我來給你們實現一個類名覆蓋的普適方案,大家不再須要繁瑣的一個頁面一個頁面去實現,或者各類修改css文件,只須要引入這個插件,自動把全部的類都覆蓋好。
提取antd的全部color相關類
我把antd-v3.19.0版本
的css文件下載到了本地,大概有2W+行代碼,從中我細心耐心的提取了全部@primaryColor
相關顏色(包括:hover :focus :active
)等。由於只保留了類下的color相關屬性,因此精簡到了900行代碼左右。
這裏就簡單截圖,不給你們展現了,想看的話地址在這theme.css。
這裏重點說明的是,:root{ ... }
下面的幾個colorVar,這也算是設計方案吧,由於這樣,我只須要獲取用戶設置的顏色,而後生成相關顏色替換colorVar變量便可。否則的話我須要寫一個正則,匹配全部的color,效率確定沒這個好
動態獲取用戶顏色,而後進行替換
這裏其實很簡單,就是改造咱們要插入的style標籤內容,具體代碼以下:
const cssVar = `
:root {
--primary-color: ${primaryColor};
--primary-hover-color: ${hoverColor};
--primary-active-color: ${activeColor};
--primary-shadow-color: ${shadowColor};
}
`;
// 給插入的標籤賦id,避免屢次插入<style>
let styleNode = document.getElementById('dynamic_antd_theme_custom_style');
if (!styleNode) {
// avoid repeat insertion
styleNode = document.createElement('style');
styleNode.id = 'dynamic_antd_theme_custom_style';
styleNode.innerHTML = `${cssVar}\n${cssContent}`;
document.getElementsByTagName('head')[0].appendChild(styleNode);
} else {
styleNode.innerHTML = `${cssVar}\n${cssContent}`;
}
複製代碼
上面的標籤內容是:
<style>${cssVar}\n${cssContent}</style>
,cssVar
是咱們定義的顏色相關的幾個變量,而後cssContent
就是我提取的全部css代碼toString()
一下成爲字符串變量。
動態獲取顏色這一塊,我使用的是react-color
這個插件,而後既然是換膚,確定應該保存用戶選擇方案,因此搭配的就是localStorage
進行客戶端緩存,最後效果就是這樣:
有人可能說了,你說了半天
@primaryColor
相關顏色,到如今仍是隻有一個@primaryColor
,是否是在這扯犢子呢,😄別急,我都說了是最重要的地方,確定是放在難點裏了。
這裏值得跟你們分享一下,難點並非從兩萬多行代碼裏抽離出全部與@primaryColor
相關的屬性,而是你須要經過選中的@primaryColor
來動態生成淺顏色的@primaryHoverColor
和深顏色的@primaryActiveColor
。
這裏使用過的人應該都明白個人意思,antd的button按鈕,a標籤等等,
:hover/:focus/:active/:visited
等等這些屬性都擁有本身的顏色,一些是與@primaryColor
相比更淺,一些是與@primaryColor
相比更深~具體看下面的動圖,hover的時候顏色更淺一些,active的時候顏色更深一些
你不能單純的把全部顏色相關屬性統一變成一個顏色,雖然那樣很簡單,可是從體驗上來說就失去了一些用戶體驗感,那樣的話還不如不作換膚了。因此接下來就詳細說一下這塊的實現過程。
去看antd的源碼能夠發現,他其實並無相關:hover :active :focus
顏色的詳細設置,而是全部顏色都是經過@primaryColor
轉換而來的。
能夠看到,它將@primaryColor
分紅了是個顏色級別,以level6
做爲分界線,<6
的顏色會相比@primaryColor
淺一些,適合:hover
這種,>6
的顏色會比@primaryColor
深一些,適合:active
這種。全部顏色都是經過colorPalette
這個方法進行生成的,因此咱們詳細要說的就是這一塊。
事先聲明以及甩鍋,我盡力去理解去嘗試了,不過最後我實現的也只是簡單的四種顏色,並無像原來那樣分紅10個級別,不喜勿噴,歡迎感興趣的提PR,弄的愈來愈好~
/**
* 下面這些代碼謹表明我我的的事先過程以及能力水平
* 我沒仔細看官方實現,因此antd肯實現的更高級
**/
// 獲取hover的淺顏色
function getHoverColor (color, index = 5) {
return tinycolor.mix(
'#ffffff',
color,
currentEasing(index) * 100 / primaryEasing
).toHexString();
}
// 獲取active的深顏色
function getActiveColor (color, index = 7) {
return tinycolor.mix(
'#333333',
color,
(1 - (currentEasing(index) - primaryEasing) / (1 - primaryEasing)) * 100
).toHexString();
}
複製代碼
上面有兩個函數,一個是獲取淺顏色,一個是獲取深顏色。兩個函數內部調用的都是一個叫作tinycolor.mix
的方法,而且咱們看一下參數就很是容易理解了,這個mix
方法其實就是讓咱們的主色@primaryColor
跟另外一個顏色去融合,好比跟#ffffff
去融合,即便我不懂代碼不懂計算機,學過畫畫的應該也都知道,若是有顏色的跟白色的混合,顏色會變淺,可是不會變成其餘色系,也就是藍色 -> 淺藍色,紅色 -> 淺紅色
等等,另外一個也同理,跟#000000
等黑灰色系融合,就會加深。接下來就看這個mix函數了
:
/* tinycolor-mix */
tinycolor.mix = function(color1, color2, amount) {
amount = (amount === 0) ? 0 : (amount || 50);
var rgb1 = tinycolor(color1).toRgb();
var rgb2 = tinycolor(color2).toRgb();
var p = amount / 100;
var rgba = {
r: ((rgb2.r - rgb1.r) * p) + rgb1.r,
g: ((rgb2.g - rgb1.g) * p) + rgb1.g,
b: ((rgb2.b - rgb1.b) * p) + rgb1.b,
a: ((rgb2.a - rgb1.a) * p) + rgb1.a
};
return tinycolor(rgba);
};
複製代碼
這裏就是將兩個顏色和一個權重輸入進去,最後輸出一個rgba的顏色值。這裏面的tinycolor是一個顏色相關的插件,感興趣的能夠去看看。
ok,而後那個權重又是什麼東西啊,這裏說實話我數學不是很好,就真看不懂了,反正他是一個貝葉斯曲線,就是爲了讓咱們的顏色變換的更平滑~其餘文章的解釋大概也就是這樣了,還有個圖片:
/* basic-easiing */
const baseEasing = BezierEasing(0.26, 0.09, 0.37, 0.18);
// 主色基線
const primaryEasing = baseEasing(0.6);
// 融合顏色的基線
const currentEasing = index => baseEasing(index * 0.1);
複製代碼
也不知道我講沒講清楚,上述繁雜的一系列操做事後,你就能根據你輸入的主色生成對應的相關主色系顏色值,而後進行cssVar替換便可~
你能夠經過以下方式進行直接使用:
npm install dynamic-antd-theme
or
yarn add dynamic-antd-theme
複製代碼
組件可設置屬性以下:
@primaryColor
相關的覆蓋<style>
標籤這篇文章發佈以後,評論區出現了螞蟻金服的夥伴,給出了人家已經開源的color計算插件,哈哈,我這費時費力的,就當是本身研究了一遍吧。後續可能會替換成ant-design官方計算出來的color,爲了更接近原版~**
這裏要說的就是,好比我設置了主題色,antd組件樣式的主題色確實修改了,可是若是是我本地的css樣式,實際上是沒有被修改的。好比我有一個Header組件,初始化背景色跟主題色一致,可是我更換主題色的時候,只變換了antd組件的主題色,並無更換我這個Header組件的主題色,這樣看起來就很突兀~以下所示:
更爲詳細的請見下方評論,感謝 @myran 掘友的點評
得知這個場景以後,我想到了解決方案,動態添加了一個屬性:themeChangeCallback
,傳入一個函數,參數是改變後的主題色,作一些覆蓋咱們本地樣式的內容就能夠了~
// 主題色修改事後把系統名稱的背景色更換
themeChangeCallback(color) {
document.getElementById('sys_name').style.backgroundColor = color;
}
...
<DynamicAntdTheme
style={themeStyle}
placement='bottomLeft'
themeChangeCallback={this.themeChangeCallback}
/>
複製代碼
嗯,這樣看起來就完美多了,仍是但願你們多提意見,若是本身有想法就PR,沒想法發現問題就評論或直接issue。都是能夠噠~
由於平時開發沒有兼容IE的習慣,因此這個組件不支持IE瀏覽器,不過羣裏一些小夥伴說本身的項目仍是須要兼容IE的,沒辦法,更新一下,兼容IE了。
目前最新版是
v0.2.4
。
有的人看完可能以爲沒什麼技術含量,就是作了不少重複大量的css操做來覆蓋類名而已,說實話,我也認可,可是我以爲有一句名言說的好:這個世界上本沒有路,走的人多了,就變成了路。
這個方案也一樣如此,每一個人都願意在本身的項目裏進行大量的複雜類替換,卻不肯意嫌麻煩去弄一套通用antd覆蓋類文件,而我只是願意把路給你們走出來,僅此而已。並非有什麼技術含量的插件,我也認可,哈哈😄。
另外,但願你們能多給star,多提issue,爲何呢,由於想作好的話一我的確定是能力有限的,不可能把全部組件全部場景都是適配好,若是你們在使用的時候能告訴我哪一個版本那個組件效果不對了,我能夠及時的修改上線。另外若是感興趣,也能夠多提pr,一塊兒維護。當前版本支持 antd version <= 3.19.0的絕大多數組件效果。
若有問題,及時聯繫,謝謝🙏。點star不迷路dynamic-antd-theme
Next.js小交流羣地址: