ant-design實現主題暗黑主題 和 亮色主題的 切換(實現網站黑白面板)

2021-05-29 07:00:02

最近在使用vite+react + ant-design 來搭建個人站點,看到網上好多網站都實現了黑白面板的切換,並且ant-design幫我們實現了三套主題色,一個預設亮白色暗黑主題緊湊主題。於是我也想來弄一弄。最後還是實現了,打包後也是ok的。

效果

在這裡插入圖片描述

思路

對於網站需要切換主題的話,一般有以下幾種辦法。

  • 使用css覆蓋的方式,由於css基於後面的css覆蓋前面的原理,所以這一點也是可以的。但是這一點對於使用less和scss的碼友來說,貌似不是一個很好的方法
  • 由於less裡面帶有一個less.jscdn,可以用來解析是html種使用less檔案,但是這個需要注意使用的順序,需要less的樣式檔案在前面,less.js的參照在後面,這個對於使用構建工具的同志來說不太友好,打包後less檔案都不見了,直接使用路徑肯定也是不行。
  • 社群成熟的兩個庫: antd-theme-webpack-pluginantd-theme-generator,對於我的專案來說貌似都不怎麼合適,首先:antd-theme-webpack-plugin這個庫是基於webpack來的,我們都知道vite是在開發環境使用esbuild,生產環境使用的是roallup來進行打包。antd-theme-generator這個庫的話,把less提升到了執行階段,我們程式碼一般會進行打包壓縮等,如果使用這個庫的話就意味著需要設定less相關的靜態資源不能被打包,不然會有問題。

基於上面的思路我做了以下的方法來進行嘗試。

我的程式碼地址是(這個地址不會改動): https://github.com/cll123456/blog

專案做了以下調整:

將ant-design的兩個主題,預設主題和暗黑主題引入到我自己的less檔案中。然後對此就可以後序實現改動主題色,例如:成功,失敗,警告等。如下:
在這裡插入圖片描述

這個引入的順序需要注意,後引入的變數會覆蓋前面的。不會自定義的會不生效
在這裡插入圖片描述

在我點選switch框的時候觸發方法。做以下嘗試

所有的嘗試都是基於下面的第二步,也就是方法,這裡面需要做啥事情

嘗試一

改變方法的時候直接來動態引入less檔案,這樣在引入暗黑主題是可以實現的,但是從暗黑主題卻切換不過來了。如下:

const handleSkin = (checked: boolean) => {
    if (checked) {
      // 明亮主題
       import('./../assets/style/index.less')
    } else {
      // 暗色主題
      import('./../assets/style/index.dark.less')
    }
  }

這個從白的可以切換到黑的原因是,黑色樣式覆蓋了前面白色的樣式,但是如果你再一次覆蓋卻不行,我估計是選擇器權重問題上,ant官方做了改動。如果需要從新切換回來也是有辦法的,在明亮主題中直接window.location.reload(),這樣是可以切換回來的,如下圖:在這裡插入圖片描述
這樣雖然實現了功能,體驗肯定是不好的,作為一名前端工程師,肯定是需要非常注重體驗的,不然職業生涯的路可能就不會很長。

嘗試二

  • 由於嘗試一不行,然後我就往import動態引入這邊考慮了,我考慮的方向是既然可以動態import引入,那麼我可以再一次改變的時候把前一次引入的給remove掉麼?
  • 但是我找遍了所有的檔案,import匯入的是無法remove掉的,import匯入是現代瀏覽器裡面的esm的語法。
  • 然後就去網上找各種方法,在ant-design pro中發現實現了這個功能,並且是無重新整理的,然後就去gitup上看人家的原始碼。功夫不負有心人,然後發現人家是動態使用link引入css的方式來實現的,那麼我也可以來通過link匯入less檔案來實現,並且使用less.js的cdn來進行解析。

新增一個addSkin的方法,畢竟需要匯入檔案,然後來查詢原來是否存在,然後進行刪除。

// 呼叫方法
const handleSkin = (checked: boolean) => {
    if (checked) {
      // 明亮主題
        addSkin("./../../src/assets/style/index.less")
    } else {
      // 暗色主題
     addSkin("./../../src/assets/style/index.dark.less")
    }
  }
// 新增面板的方法
function addSkin(path: string) {
  let head = document.getElementsByTagName("head")[0];
  const getLink = head.getElementsByTagName('link');
  // 查詢link是否存在,存在的話需要刪除dom
  if (getLink.length > 0) {
    for (let i = 0, l = getLink.length; i < l; i++) {
      if (getLink[i].getAttribute('data-type') === 'theme') {
        getLink[i].remove();
      }
    }
  }
  // 查詢script是否存在
  const getScript = head.getElementsByTagName('script');
  if (getScript.length > 0) {
    for (let i = 0, l = getScript.length; i < l; i++) {
      if (getScript[i].getAttribute('data-type') === 'theme') {
        getScript[i].remove();
      }
    }
  }

  // 最後加入對應的主題和載入less的js檔案
  let link = document.createElement("link");
  link.dataset.type = "theme";
  link.href = path;
  link.rel = "stylesheet";
  link.type = "text/css";
  head.appendChild(link);
  // 這個less.js一定要放到後面才行
  let script = document.createElement('script');
  script.type = 'text/javascript';
  script.dataset.type = 'theme';
  script.src = 'https://cdn.bootcdn.net/ajax/libs/less.js/4.1.1/less.js'
  head.appendChild(script)
}

這種方法是動態改變link標籤的樣式來實現的,在生產環境是沒有任何問題,但是在開發環境就不行了,打包後路徑不存在。肯定是不行的,接下來我就去找vite如何靜態資源複製到打包的檔案,方法找到了。但是我的less裡面參照了antd裡面的less,裡面的也不用打包? 我覺得不太好,因此再一次放棄。

嘗試三

既然直接使用less檔案不行,那我可以使用css不,和ant-design pro裡面一樣的,我也來參照css檔案,接下來就往這個方向。
我直接列印了,import dark from './xxxx'.less 發現既然是一個字串。
在這裡插入圖片描述
是編譯好的字串,那我直接使用style標籤就好了。說幹就往下幹。

import dark from './../assets/style/index.dark.less'
import lighter from './../assets/style/index.less'

// 呼叫方法
const handleSkin = (checked: boolean) => {
    if (checked) {
      // 明亮主題
        addSkin(lighter)
    } else {
      // 暗色主題
     addSkin(dark)
    }
  }
// 新增面板的方法
function addSkin(content: string) {
  let head = document.getElementsByTagName("head")[0];
  const getStyle = head.getElementsByTagName('style');
  // 查詢style是否存在,存在的話需要刪除dom
  if (getStyle.length > 0) {
    for (let i = 0, l = getStyle.length; i < l; i++) {
      if (getStyle[i].getAttribute('data-type') === 'theme') {
        getStyle[i].remove();
      }
    }
  }
  // 最後加入對應的主題和載入less的js檔案
  let styleDom = document.createElement("style");
  styleDom.dataset.type = "theme";
  styleDom.innerHTML = content;
  head.appendChild(styleDom);
}
  • 這裡有一個細節就是,樣式匯入必須在頂部匯入,不然vite會檢測不到,不能使用動態匯入,打包會經過treeshake去掉.
  • 其實這裡還有一個問題,那就是css打包後會比較大,畢竟引入了兩份,這個問題就留給碼友了,自己去vite獲取其他的構建工具(webpack, gulp等)上找靜態資源太大怎麼處理。
    在這裡插入圖片描述

總結

在真實的偵錯中肯定是不止這三遍嘗試的,這裡只記錄走向成功的關鍵三步。More interest, less interests (多一些興趣愛好的嚮往,少一些功名利祿的追求)