React + Antd 动态换主题实现
搭建React工程
首先按照React官网的创建工程的流程使用create-react-app脚手架创建一个React工程,然后按照antd官网推荐的craco(一个对 create-react-app 进行自定义配置的社区解决方案)来进行高级配置(这里按照antd官网教程一步一步完成即可)
自定义主题
安装 craco-less
并修改 craco.config.js
文件如下。
注意:modifyVars必须配置为一个空对象,如果传值会导致动态更换主题失败
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { },
//modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
安装webpack-theme-color-replacer插件
yarn add webpack-theme-color-replacer@1.3.22
由于craco配置的时候已经引入了craco-less插件,因此这里不需要再继续安装less以及less-loader
配置插件
在src目录下创建utils文件夹,以及plugin.config.js以及utils.js两个文件
安装完成后对插件进行配置生成,然后暴露修改主题的方法updateTheme
plugin.config.js是配置webpack-theme-color-replacer
plugin.config.js
const ThemeColorReplacer = require("webpack-theme-color-replacer");
const generate = require("@ant-design/colors").generate;
// generate方法和ant-design-vue获取方式的区别
// react: require("@ant-design/colors").generate;
// vue: require('@ant-design/colors/lib/generate').default
const getAntdSerials = (color) => {
// 淡化(即less的tint)
const lightens = new Array(9).fill().map((t, i) => {
return ThemeColorReplacer.varyColor.lighten(color, i / 10);
});
const colorPalettes = generate(color);
const rgb = ThemeColorReplacer.varyColor
.toNum3(color.replace("#", ""))
.join(",");
return lightens.concat(colorPalettes).concat(rgb);
};
const themePluginOption = {
fileName: "css/theme-colors-[contenthash:8].css",
matchColors: getAntdSerials("#1890ff"), // 主色系列
// 改变样式选择器,解决样式覆盖问题
changeSelector(selector) {
switch (selector) {
case ".ant-calendar-today .ant-calendar-date":
return (
":not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)" +
selector
);
case ".ant-btn:focus,.ant-btn:hover":
return ".ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)";
case ".ant-btn.active,.ant-btn:active":
return ".ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)";
case ".ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon":
case ".ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon":
return ":not(.ant-steps-item-process)" + selector;
case ".ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover":
case ".ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover":
return ".ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover";
case ".ant-menu-horizontal > .ant-menu-item-selected > a":
case ".ant-menu-horizontal>.ant-menu-item-selected>a":
return ".ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a";
case ".ant-menu-horizontal > .ant-menu-item > a:hover":
case ".ant-menu-horizontal>.ant-menu-item>a:hover":
return ".ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover";
default:
return selector;
}
},
};
const createThemeColorReplacerPlugin = () =>
new ThemeColorReplacer(themePluginOption);
module.exports = createThemeColorReplacerPlugin;
plugin.config.js暴露后需要添加为webpack的-plugin
查阅资料后发现,在craco.config.js里面是可以配置webpack的(类似于vue在vue.config.js里面配置webpack)
配置后的craco.config.js
const CracoLessPlugin = require('craco-less');
const createThemeColorReplacerPlugin = require("./src/utils/plugin.config");
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { },
// modifyVars: { '@primary-color': '#0082c3' },
javascriptEnabled: true,
},
},
},
},
],
webpack:{
// 这里面是webpack的配置
plugins: [createThemeColorReplacerPlugin()],
}
};
utils.js其实是我个人封装的工具模块,这边把updateTheme封装进去
import client from "webpack-theme-color-replacer/client";
import { generate } from "@ant-design/colors";
// 同样的 antd-react和antd-vue获取generate方法的路径是不同的
function getAntdSerials(color) {
// 淡化(即less的tint)
const lightens = new Array(9).fill().map((t, i) => {
return client.varyColor.lighten(color, i / 10);
});
// colorPalette变换得到颜色值
const colorPalettes = generate(color);
const rgb = client.varyColor.toNum3(color.replace("#", "")).join(",");
return lightens.concat(colorPalettes).concat(rgb);
}
function changeColor(newColor) {
var options = {
newColors: getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
changeUrl(cssUrl) {
return `/${cssUrl}`; // while router is not `hash` mode, it needs absolute path
},
};
return client.changer.changeColor(options, Promise);
}
export default {
// 最后将修改主题的方法updateTheme暴露出去
updateTheme(newPrimaryColor) {
const hideMessage = () => console.log("正在切换主题!", 0);
changeColor(newPrimaryColor).finally((t) => {
setTimeout(() => {
hideMessage();
});
});
},
};
然后在任意组件里面地方都可以引入utils并且调用里面的updateTheme方法改变主题了(注:updateTheme方法传参直接传入十六进制的颜色,不要传其他格式的颜色,因为在做颜色淡化例如less的fade方法事时可能会报错)