Little-Gee / blog

13 stars 11 forks source link

ant design 动态切换主题 #2

Open Little-Gee opened 5 years ago

Little-Gee commented 5 years ago

采用antd-theme-generator这个插件,使用 lessmodifyVars 来更改主题

(也有 webpack插件形式——antd-theme-webpack-plugin

主要就是通过浏览器编译 less 来实现更改主题,生成一个 color.less 文件

版本:

create-react-app: 3.0.1
"react": "^16.8.6",
"webpack": "4.29.6",
"antd": "^3.18.1",
"antd-theme-generator": "^1.1.6",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"react-color": "^2.17.3",

less、ant design等配置省略…… 为了方便(偷懒),直接采用之前搭的项目

首先安装 antd-theme-generator

npm install --save antd-theme-generator

scripts 文件夹下添加脚本 scripts/generateColorLess.js

const path = require('path');
const { generateTheme } = require('antd-theme-generator');

const options = {
    stylesDir: path.join(__dirname, "../src/styles"),
    antDir: path.join(__dirname, "../node_modules/antd"),
    varFile: path.join(__dirname, "../src/styles/variables.less"),
    mainLessFile: path.join(__dirname, "../src/styles/index.less"),
    themeVariables: [
        "@primary-color",
        "@secondary-color",
        "@text-color",
        "@text-color-secondary",
        "@heading-color",
        "@layout-body-background",
        "@layout-header-background",
        "@border-radius-base"
    ],
    outputFilePath: path.join(__dirname, "../public/color.less")
};

generateTheme(options)
    .then(less => {
        console.log("Theme generated successfully");
    })
    .catch(error => {
        console.log("Error", error);
    });

注意 options 中的路径要与自己项目中的路径一致,其中 index.lessvariables.less 需要自己创建

src 下创建文件 src/styles/index.lesssrc/styles/variables.less,这两个文件用于定义全局的主题样式

// index.less
.base-color {
    color: @primary-color;
}
// variables.less
@import "~antd/lib/style/themes/default.less";
@primary-color: #1890ff;

注意:

然后修改 public/index.html

<body>
    <!-- 中间这几行是需要添加的 -->
    <link rel="stylesheet/less" type="text/css" href="/color.less" />
    <script>
        window.less = {
            async: false,
            env: 'production'
        };
    </script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script>
    <!-- 中间这几行是需要添加的 -->
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root" ></div>

修改 package.json 中的脚本

"scripts": {
    "start": "node ./scripts/generateColorLess.js && node scripts/start.js",
    "build": "node ./scripts/generateColorLess.js && node scripts/build.js",
    "test": "node scripts/test.js"
},

至此配置的部分就差不多了,接下来就是具体使用,和官方一样用的是 react-color

npm install --save react-color

具体使用方法可以参考官网,我这里直接“参考”了 ant design 的用法(逃

新建文件 src/color/ColorPicker.jsx

import React, { Component } from "react";
import { ChromePicker, SketchPicker } from "react-color";

const noop = () => {};

const pickers = {
    chrome: ChromePicker,
    sketch: SketchPicker
};

export default class ColorPicker extends Component {
    static defaultProps = {
        onChange: noop,
        onChangeComplete: noop,
        position: "bottom"
    };

    static getDerivedStateFromProps(props) {
        if ("color" in props) {
            return {
                color: props.color
            };
        }
        return null;
    }

    state = {
        displayColorPicker: false
    };

    handleClick = () => {
        const { displayColorPicker } = this.state;
        this.setState({ displayColorPicker: !displayColorPicker });
    };

    handleClose = () => {
        this.setState({ displayColorPicker: false });
    };

    handleChange = color => {
        const { onChange } = this.props;
        this.setState({ color: color.hex });
        onChange(color.hex, color);
    };

    handleChangeComplete = color => {
        const { onChangeComplete } = this.props;
        this.setState({ color: color.hex });
        onChangeComplete(color.hex);
    };

    render() {
        const { small, type, position } = this.props;
        const { color, displayColorPicker } = this.state;
        const Picker = pickers[type];
        const styles = {
            color: {
                width: small ? "80px" : "120px",
                height: small ? "16px" : "24px",
                borderRadius: "2px",
                background: color
            },
            swatch: {
                padding: "4px",
                background: "#fff",
                borderRadius: "2px",
                boxShadow: "0 0 0 1px rgba(0,0,0,.1)",
                display: "inline-block",
                cursor: "pointer"
            },
            popover: {
                position: "absolute",
                zIndex: "2"
            },
            cover: {
                position: "fixed",
                top: "0px",
                right: "0px",
                bottom: "0px",
                left: "0px"
            },
            wrapper: {
                position: "inherit",
                zIndex: "100"
            }
        };

        if (position === "top") {
            styles.wrapper.transform = "translateY(-100%)";
            styles.wrapper.paddingBottom = 8;
        }

        const swatch = (
            <div style={styles.swatch} onClick={this.handleClick}>
                <div style={styles.color} />
            </div>
        );
        const picker = displayColorPicker ? (
            <div style={styles.popover}>
                <div style={styles.cover} onClick={this.handleClose} />
                <div style={styles.wrapper}>
                    <Picker
                        {...this.props}
                        color={color}
                        onChange={this.handleChange}
                        onChangeComplete={this.handleChangeComplete}
                    />
                </div>
            </div>
        ) : null;

        if (position === "top") {
            return (
                <div>
                    {picker}
                    {swatch}
                </div>
            );
        }
        return (
            <div>
                {swatch}
                {picker}
            </div>
        );
    }
}

然后就可以使用了,比如 App.js

import React, { Component } from "react";
import { Button, message } from "antd";
import ColorPicker from "./color/ColorPicker";

class App extends Component {
    state = {
        color: "#1890ff"
    };

    handleColorChange = color => {
        window.less
            .modifyVars({
                "@primary-color": color
            })
            .then(() => {
                this.setState({ color });
            })
            .catch(error => {
                message.error(`Failed to update theme`);
            });
    };

    render() {
        const { color } = this.state;
        return (
            <div style={{ marginTop: 100 }}>
                <div className="base-color">文字</div>
                <Button type="primary">Button</Button>
                <div style={{ marginTop: 30 }}>
                    <ColorPicker
                        type="sketch"
                        small
                        color={color}
                        position="bottom"
                        presetColors={[
                            "#F5222D",
                            "#FA541C",
                            "#FA8C16",
                            "#FAAD14",
                            "#FADB14",
                            "#A0D911",
                            "#52C41A",
                            "#13C2C2",
                            "#1890FF",
                            "#2F54EB",
                            "#722ED1",
                            "#EB2F96"
                        ]}
                        onChangeComplete={this.handleColorChange}
                    />
                </div>
            </div>
        );
    }
}

export default App;

效果图: GIF

项目参考地址

最后,附上 antd-theme-generatorgithub地址

也有更方便的 webpack 插件形式 antd-theme-webpack-plugin 及其 github地址

seed-fe commented 3 years ago

您好,把您这个项目clone下来以后,npm start跑起来在生成color.less文件的时候会报错LessError: error evaluating function darken: color.toHSL is not a function,无法生成color.less,请问您遇到过这个问题吗?

Little-Gee commented 3 years ago

您好,把您这个项目clone下来以后,npm start跑起来在生成color.less文件的时候会报错LessError: error evaluating function darken: color.toHSL is not a function,无法生成color.less,请问您遇到过这个问题吗?

以前没注意过,你试试升级下依赖? https://github.com/mzohaibqc/antd-theme-generator/issues/66