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;
效果如图
注意!!!
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浏览器中)。