效果如下:
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
三個檔案;
- antd.theme.less:用來放置覆寫
UI 元件庫樣式;antd
(antd 官方樣式,可以檢視這篇文章的評論
https://www.jianshu.com/p/87023e7f34c6
)
- custom.theme.less:放置掃描項目所有自定義主題的樣式;
- components.less:引入 antd.theme.less 和 custom.theme.less,如下:
@import "./antd.theme.less";
@import "./custom.theme.less";
說明:為啥是 public 目錄呢?因為 Umi 項目,public 目錄下所有檔案會被 copy 到輸出路徑,也就是相關的資源,是被直接放到項目根目錄。
具體如下圖:
步驟三:編寫 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
導出,如下圖:
步驟四:編寫 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 樣式寫法。
注意:自定義檔案 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 資源
該文章是本人之前寫過的兩篇文章彙總,用興趣的同學,可以檢視原來的
文章,了解更多的思路。
- GitHub 源碼: umi-antd-dynamic-theme master 分支