史上最簡單的Ant-Design換膚方案

先當作果

線上預覽地址:Dynamic-Antd-Theme-Demo css

看起來是否是還能夠,若是你知道如何使用,相信你會更以爲我不是一個標題黨。

// 如何使用
import DynamicAntdTheme from 'dynamic-antd-theme';

render() {
    ...
    <DynamicAntdTheme />
    ...  
}
複製代碼

沒錯,就是這麼簡單,爲了方便你們,我已經弄成了即插即用的組件~歡迎你們star + issue + pr。dynamic-antd-themehtml

爲何要作ant-design的換膚方案

這裏是我很想說的,我尚未自負到或者說貪心到有能力去實現一個完美的合理的antd動態換膚方案。好比開源社區裏的antd-theme-webpack-pluginantd-theme-generator就是很成熟的方案,利用webpack的方式在頁面裏經過引入less.js實現動態換膚,換的很完全。前端

那麼爲何我還要來搞一個所謂的動態換膚呢?理由以下:react

  • 首先,上面兩個方案並不能覆蓋全部的場景,他所須要的入口文件index.html,做爲nextjs黨的我,就怎麼找也找不到,因此在個人nextjs腳手架項目裏就沒有配置成功。
  • 其次,我的認爲配置以及使用稍微繁瑣以及額外引入太多內容。html文件必須引入less.js文件,由於這樣才能使用window.less對主題進行動態更換。還有配置的時候各類css文件也是不太清楚意義。
  • 最後,也是最重要的。有人在個人文章裏提問了,也就是與Next-Antd-Scaffold這個腳手架相關的文章,感興趣的能夠去看看前面我寫的文章。小夥伴沒有配置成功,又沒有思路,怎麼辦呢,做爲熱心做者,確定要幫忙解決呀~

這裏安利一波,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,而後那個權重又是什麼東西啊,這裏說實話我數學不是很好,就真看不懂了,反正他是一個貝葉斯曲線,就是爲了讓咱們的顏色變換的更平滑~其餘文章的解釋大概也就是這樣了,還有個圖片:

這裏更加深刻的我就不說了,也說不明白,反正我是照貓畫虎畫出來的。須要強調一下的是曲線須要選中一個基線,antd的基線是以下:

/* 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相關的覆蓋
  • 其餘覆蓋仍然須要:global或者組件內覆蓋這種方式
  • 沒有作全部組件的效果測試,可能存在某些場景效果出現誤差,提issue會及時解決
  • 會向項目內暴力插入一段900行左右的<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小交流羣地址:

相關文章
相關標籤/搜索