天天看點

umi + antd 動态主題色

效果如下:

umi + antd 動态主題色

1 前言

本人項目使用的 ui 庫為 antd,樣式為 less。

主題色的變化,antd 官網提供了相關的方案:

定制主題

,但是,該方案是靜态的換膚,也就是已經知道系統需要什麼樣的主題色,根據相關的配置,antd 自動做轉化。

這篇文章講解的是動态主題色的變化,也就是,頁面可能會有10種,或者20種顔色需要切換,不知道到底有多少種顔色;同時,文檔也考慮到多人協助開發,開發人員隻需要按照約定方式去編寫樣式、主題檔案名、目錄等命名規範即可。

主要思路:動态插入樣式,覆寫系統已經編譯好的相關樣式,包括 UI 元件庫 和 自定義樣式。

2 實作

步驟一:在 Umi 裡配置主題

如果你在使用 

Umi

,那麼可以很友善地在項目根目錄的 

.umirc.ts

 或 

config/config.ts

 檔案中 

theme

 字段進行主題配置。

theme

 可以配置為一個對象或檔案路徑。

"theme": {
  "primary-color": "#1DA57A",
},      

或者 

一個 js 檔案

"theme": "./theme.js",      

本人使用的是公司自己的架構,基于 Umi 進行二次封裝,是以,在 本目錄下的

config/theme.ts

配置

@primary-color

,就實作了預設主題色的配置。

步驟二:建立相關目錄和檔案

在根目錄下,建立

public

目錄,引入

less.4x.min.js

,具體在項目中 GitHub 代碼庫中下載下傳;同時建立

styles

目錄,建立

antd.theme.less

components.less

custom.theme.less

三個檔案;

  • custom.theme.less:放置掃描項目所有自定義主題的樣式;
  • components.less:引入 antd.theme.less 和 custom.theme.less,如下:
@import "./antd.theme.less";
@import "./custom.theme.less";      

說明:為啥是 public 目錄呢?因為 Umi 項目,public 目錄下所有檔案會被 copy 到輸出路徑,也就是相關的資源,是被直接放到項目根目錄。

具體如下圖:

umi + antd 動态主題色

步驟三:編寫 scripts/theme/themeScripts.js 腳本

建立 scripts/theme/themeScripts.js 檔案,編寫如下腳本,該腳本的主要作用是掃描項目中所有的

xxx.theme.less

檔案,寫入到

custom.theme.less

檔案中。

// 主題色腳本
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const { promisify } = require('util');
const themeVars = require('./../../config/theme');
const glob = promisify(require('glob'));
const root = './';
const customPath = './public/styles/custom.theme.less';
Main();
async function Main() {
  init();
  start();
}
function init() {
  // 清空
  fs.writeFileSync(customPath, '', 'utf8');
  // 主題色變量初始值,跟 src/utils/index.ts 中 less.modifyVars 定義的變量相對應。
  for(let key in themeVars) {
    fs.appendFileSync(customPath, `${key}: ${themeVars[key]};\n`, 'utf8');
  }
}
async function start() {
  const rootPath = path.resolve(root);
  const fileList = await glob('src/**/*.theme.less', { cwd: rootPath, ignore: ['node_modules/**', 'src/.umi/**', 'src/.umi-production/**'] });
  // global.theme.less 在最前面,優先級最低
  const otherList = [];
  const globalFile = fileList.filter(file => {
    if (file.indexOf('global.theme.less') > -1) {
      return true;
    }
    otherList.push(file);
    return false;
  });
  globalFile.concat(otherList).forEach((filePath) => {
    writeFile(rootPath, filePath);
  });
  console.info(chalk.greenBright('恭喜您, 主題檔案掃描完成!!!')); // eslint-disable-line
}
// 寫檔案
function writeFile(rootPath, filePath) {
  const bufferContent = fs.readFileSync(path.resolve(rootPath, filePath));
  const content = bufferContent.toString('utf8');
  fs.appendFileSync(customPath, `\n// ${filePath}\n${content}`, 'utf8');
}
module.exports = Main;      

注意:node 使用的是 CommonJS 伺服器端 js 子產品化的規範,是以對子產品化導入導出需要使用 module.exports,主題色變量直接使用了項目初始化配置的

config/theme.ts

變量,是以,需要把檔案從 ts 改為 js,同時

config/theme.ts

的導出需要改為

module.exports

導出,如下圖:

umi + antd 動态主題色

步驟四:編寫 utils 主題色方法

下面的方法主要是使用

less.js

,動态插入自定義的相關樣式,利用

less.modifyVars

,傳入相關動态主題色參數,改變自定義的相關樣式。

根據如下代碼,自定義的樣式檔案為

/styles/components.less

,該檔案是作為統一的入口,引入 antd 和 自定義樣式。

自定義的變量有

@header-bar-visible

和 @primary-color;

function _changeTheme(themeColor: string) {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  if (!(window as any).less) return;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  (window as any).less.modifyVars({
    '@primary-color': themeColor,
    '@header-bar-visible': 'visible',
  });
}
let lessNodesAppended: boolean = false;
/**
 * 動态更改主題色
 * @param {string} themeColor 主題顔色
 */
export function onChangeTheme(themeColor: string) {
  if (!lessNodesAppended) {
    // 插入 less.js,和 顔色主題.less
    const lessConfigNode = document.createElement('script');
    const lessScriptNode = document.createElement('script');
    const lessStyleNode = document.createElement('link');
    lessStyleNode.setAttribute('rel', 'stylesheet/less');
    lessStyleNode.setAttribute('href', '/styles/components.less'); // public 目标下
    // https://lesscss.org/usage/#api
    // env: 'production' development
    lessConfigNode.innerHTML = `
        window.less = {
          env: 'production',
          async: true,
          javascriptEnabled: true
        };
        `;
    lessScriptNode.src = '/less.4x.min.js';
    lessScriptNode.async = true;
    lessScriptNode.onload = () => {
      _changeTheme(themeColor);
      lessScriptNode.onload = null;
    };
    document.body.appendChild(lessStyleNode);
    document.body.appendChild(lessConfigNode);
    document.body.appendChild(lessScriptNode);
    lessNodesAppended = true;
  } else {
    _changeTheme(themeColor);
  }
}      

3  package.json 引入執行主題色腳本

在 package.json 的 scripts 引入執行主題色腳本,同時,在 start 和 build 之前,每次自動執行掃描主題色,如下:

"scripts": {
  "start": "npm run theme && umi dev",
  "build": "npm run theme && umi build",
  "theme": "node scripts/theme/themeScripts.js",
},      

同時,安裝腳本需要的庫,glob 和 chalk:

npm i glob chalk -S      

4 頁面元件樣式編寫

經過以上步驟,就已經大功告成了,開發人員隻需要編寫

xxx.theme.less

即可,不再需要編寫其他配置檔案。

約定,相關的主題顔色需要抽成

xxx.theme.less

,如編寫 List 元件,List.less 和 List.theme.less,如下圖:

less 樣式需要使用 prefix,不能使用

import styles from './Less.less'

這種寫法,因為如果使用該寫法,

則樣式編譯會被加上 hash

,動态主題色覆寫就無法覆寫了。antd 官網元件也是使用 prefix 樣式寫法。

umi + antd 動态主題色

注意:自定義檔案 xxx.theme.less 使用 css 寫法,而不是采用

@prefix

字首變量寫法,因為

寫法有時候會失效,正常樣式寫法示例如下:

.c-list-more{
  color: @primary-color;
}
.c-list-more:hover{
  color: @primary-color;
}
.c-list-item .title::before{
  background-color: @primary-color;
}
.c-list-item .title:hover{
  color: lighten(@primary-color, 15%);
}
.c-list-item .desc{
  color:  @primary-color;
}      

5 資源

該文章是本人之前寫過的兩篇文章彙總,用興趣的同學,可以檢視原來的

文章

,了解更多的思路。

繼續閱讀