React使用css变量动态改变主题

1,284 阅读3分钟

React使用less动态改变主题

需求

公司一个项目,需要给整个网站动态的改变主题,框架用的是React,less写的样式,查找了很多方案,最后得到当前的实现方式,其实还有一些不完整,但是修改代码的权限不多,只能在现有的功能下做。

弃案 less.modifyVars改变主题色

参考了antd的样式文件,尝试定义变量,从不同的文件中引入样式变量

// dark.less
@color:black;

// light.less
@color:skublue;

// themes.less
@themes:dark;
@import './@{themes}.less';

// style.less
@import './themes.less';

box{
	background-color:@color;
}

如此一来我只需要改变themes.less文件中的@themes变量,就可以改变文件的引入,从而达到动态引入不同的文件,这样只需要多写几个主题样式就可以了。

但是,我想的十分天真,less.modifyVars的使用条件十分苛刻,对于一个大型项目来说,那些对webpack的改变是无法接受的(同时我也没有能力去做)。使用less-loader插件的项目,在编译阶段就已经把less编译固定了,想后期改变难上加难,除非你将所有的样式文件全部写在public文件夹下(当然这是不可能的)。

最终方案

浏览网页时,发现css中的var()函数十分好用,也十分贴合我的想法,于是我将他和less变量结合起来,做了一个盖中盖的主题切换方案,仅供大家参考,里面也有很多坑,会在本节末尾列出。

// themes.less

// 选择器会自动匹配到:root上
// 只需要改变:root的data-themes属性即可定义不同的--color
[data-themes='default'] {
    --color: #123456;
}

[data-themes='dark'] {
    --color: #654321;
}
// 使用var函数获取--color的值
// 这样其他less文件中都可以使用该变量
@color: var(--color, #123456);

// app.less
@import './themes.less';

.box {
	// 也可以使用
    // background-color: var(--color),但是使用less变量更简洁不是
    background-color: @color;
    width: 100px;
    height: 100px;
}

// app.tsx

import './styles/app.less'


function App() {
    return (
        <div>
            <div className="box"></div>
            // 点击改变全局样式
            <button
                onClick={() => {
                  const root= document.querySelector(':root')
                  if(root){
                    root.setAttribute('data-themes', 'default');
                  }
                }}
            >
                default
            </button>
            <button
                onClick={() => {
                  const root= document.querySelector(':root')
                  if(root){
                    root.setAttribute('data-themes', 'dark');
                  }
                }}
            >
                dark
            </button>
        </div>
    );
}

export default App;

效果如图

themes.change.gif

注意!!!

less中函数的使用只能对确定的变量值进行计算,而不能计算表达式。

@size: var(--size, 12px);

.box{
	font-size: @size * 2; // 报错,因为less只能识别确定的量,而非表达式
    font-size: calc( @size * 2); //正确,使用了css的方法能正确识别var表达式
}

这也是我使用的方案十分不方便的地方,如果有更好的方案的话欢迎讨论。

兼容性

本来项目需要兼容IE的,但是后面在产品经理的努力下,取消了主题切换功能在IE中的实现。现在把当时解决的方法与思路总结一下。

css-vars-ponyfill

在面向搜索引擎编程过程中,发现了css-vars-ponyfill这个库,它的实现原理是将var()替换为浏览器可以识别的样式。

具体用法如下:

// index.tsx

// 引入
import cssVars from 'css-vars-ponyfill'; 

// 使用方法
cssVars({
    watch: true,
    onlyLegacy: true,
});

ReactDOM.render(<App />, document.getElementById('root'));

cssVars的具体用法请自行搜索,但是用它来实现主题替换,性能实在是太差了(在IE浏览器中)。