前言
刚刚搞完的一个项目。electron写的一个客户端软件,主要用来给公司客服与访客聊天用的。
做出第一个版本后,客服人员试用了几天,给我说,能不能加几个主题选,老是一个颜色有点腻。我脸上笑呵呵,心里一万那啥跑过(我勒个去,早干嘛不说,你是不知道我得改多少东西吗)。不过,既然需求提出来了,我就只能搞搞了。
一开始没想这个东西有多难。主题切换不就是样式切换嘛,我多搞几套内置样式就好了。本着这样得想法,很快就做出来了。
最早的实现方案,固定多几套主题
iview是vue得一个ui框架,css用的是less。在项目里头新建一个theme.less的文件,然后在main.js中引入
main.js-----------
import iView from 'iview';
import 'iview/dist/styles/iview.css';
import './theme.less'; //在iview的样式后面引入
theme.less--------
// 默认样式
.default-theme{
@mainColor:#16c497;
@mainColorAct:rgba(23, 206, 159,0.3);
.theme(@mainColor,@mainColorAct);
}
//蓝色
.lan-theme{
@mainColor:rgba(58, 95, 157,1);
@mainColorAct:rgba(58, 95, 157,0.3);
.theme(@mainColor,@mainColorAct);
}
//红色
.red-theme{
@mainColor:rgba(209, 62, 57,1);
@mainColorAct:rgba(209, 62, 57,0.3);
.theme(@mainColor,@mainColorAct);
}
//天空蓝
.qlan-theme{
@mainColor:rgba(86, 177, 252,1);
@mainColorAct:rgba(86, 177, 252,0.3);
.theme(@mainColor,@mainColorAct);
}
.theme(@mainColor,@mainColorAct){
@import '~iview/src/styles/index.less';
@primary-color: @mainColor;
.section{
.sidebar{
background: @mainColor;
}
}
.title .btn > p:hover {
color: @mainColor;
}
.title .openurl{
img{
filter: drop-shadow(85px 0 @mainColor);
}
&:hover img{
filter: drop-shadow(85px 0 #56b1fc);
}
}
.chatMain{
.charRenshuList{
h2{
color: @mainColor;
}
p{
&.hover{
color:@mainColor;
// background: @mainColorAct;
}
}
}
.charWindowList{
.infoXieru{
.gongju{
i{
&:hover{
color: @mainColor;
}
}
}
}
}
}
.charInfo {
.item{
h1{
color: @mainColor;
background: @mainColorAct;
}
}
.ivu-collapse > .ivu-collapse-item > .ivu-collapse-header {
background: @mainColorAct;
}
}
.charInfo .kjyy li .icon-bianji, .charInfo .kjyy i{
color: @mainColor;
}
.kjyy .edit_kjyy .titles{
background: @mainColor;
}
.kjyy .edit_kjyy .content .caozuo span:nth-child(2) {
background: @mainColor;
border: 1px solid @mainColor;
}
.el-button:active ,.el-button:hover {
color: @mainColor;
border-color: @mainColor;
outline: 0;
}
.chatMain .charWindowList .infoXieru .sumBtn > span{
background: @mainColor;
}
/*定义滑块 内阴影+圆角*/
::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: @mainColor;
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.infoShow{
.char{
&.charOther{
.info{
&::before{
border-left: 1px solid @mainColor;
border-bottom: 1px solid @mainColor;
}
}
}
&.charOwn{
.info{
background: @mainColor;
&::before{
border-right: 1px solid @mainColor;
border-top: 1px solid @mainColor;
background: @mainColor;
}
}
}
.info{
border: 1px solid @mainColor;
.time{
color: @mainColor;
}
}
}
}
.chatMain .charRenshuList p > .ivu-icon {
background: @mainColor;
}
.settingBox .list_title .hover {
color: @mainColor;
}
.title>.ivu-icon{
color: @mainColor;
}
}
界面点击不同的主题按钮,更换class名。这应该是主题类需求最古老最常见的做法了。百度上只要一搜前端切换主题、iview切换主题、elementui切换主题,你能搜出来的,90%就是这种方案。它所存在的优缺点:
- 优点
- 实现容易,就是简单class类名切换
- 缺点
- 本地会存在多套主题样式,增加了代码体积。
- 主题切换不够灵活,不能想用什么颜色就用什么颜色
动态主题切换
功能完成后,客服用了一个月,又跑来说,这个能不能不要固定几套主题,就是我想换什么颜色就什么颜色。我脸上笑呵呵,心里一万那啥跑过(我勒个去,不太容易搞啊)。没办法,需求提了,还得接着搞搞。
我可能是个文科生的缘故,没啥思路搞这个。百度上搜搜,找到一个相同功能的,里头讲的很好,是个大神,他就是 花裤衩。一开始我是在SegmentFault网站看到他的文章手摸手,带你用vue撸后台 系列 ,后面发现在掘金也有,哈哈哈😁。
vue-element-admin动态换肤,引用下他的文章原话:
简单说明一下它的原理: element-ui 2.0 版本之后所有的样式都是基于 SCSS 编写的,所有的颜色都是基于几个基础颜色变量来设置的,所以就不难实现动态换肤了,只要找到那几个颜色变量修改它就可以了。 首先我们需要拿到通过 package.json 拿到 element-ui 的版本号,根据该版本号去请求相应的样式。拿到样式之后将样色,通过正则匹配和替换,将颜色变量替换成你需要的,之后动态添加 style 标签来覆盖原有的 css 样式。
虽然我是用的iview,但是和element也差不多,less与scss也差不多,这个思路也许在我这里也能行。写着写着,嗯不对,这里有个请求,是不是我每次换肤都要请求一遍他的最新样式回来,然后在替换,我要是不停的切换,不是会产生很多的网络请求吗?(当时脑子抽风,没想请求缓存😂),不行不行,绝对不行,我就换个主题,绝对不能去请求。
就这样,暂时放弃这个思路。又想到,css好像有个自定义属性,也叫css变量,利用它是不是能搞,也不废话,试它一试。
html {
--theme: #ff0000;
}
a,p,h1,h2{color:var(--theme)};
简单试用了下css原生变量可行之后,就他娘的兴奋,less不是也用变量吗,我那css的变量替换调less的变量不就行了嘛。然后,就狠狠的打脸了,替换完立马编译报错,我勒个去,less颜色混合器根本不认这个原生变量。我幼小的心灵受到了打击,还能不能好好玩了...
没办法了,接着换别的想法,那我能不能手动写less的混合器呢,搞搞。
查看iview官方文档,发现主题变量与less的Fade函数(透明度)、shade函数(混合颜色与黑色)、tint函数(混合颜色与白色)。默认主题的颜色值:#2d8cf0。在这之前,先将16进制颜色值换成rgba值。在过滤出r、g、b、a的数值。
const value = val.replace('rgba(','').replace(')','').split(',');
var theme_obj = {
theme:[...value].map(Number), //主题原色
fade90:[...value].map(Number), //90%透明
fade20:[...value].map(Number), //20%透明
shade5:[...value].map(Number), //与黑色混合5%
tint20:[...value].map(Number), //与白色混合20%
tint80:[...value].map(Number), //与白色混合80%
tint90:[...value].map(Number), //与白色混合90%
};
编写less函数
fade函数
theme_obj.fade90[3] = .9;
theme_obj.fade20[3] = .2;
shade函数
//转换rgb三个数值
for(let i=0;i<3;i++){
theme_obj.shade5[i] = Math.ceil(theme_obj.shade5[i]-theme_obj.shade5[i]*0.05);
}
tint函数
//转换rgb三个数值
for(let i=0;i<3;i++){
theme_obj.tint20[i] = Math.ceil(theme_obj.tint20[i]+(255*0.2)-(theme_obj.tint20[i]*0.2));
theme_obj.tint80[i] = Math.ceil(theme_obj.tint80[i]+(255*0.8)-(theme_obj.tint80[i]*0.8));
theme_obj.tint90[i] = Math.ceil(theme_obj.tint90[i]+(255*0.9)-(theme_obj.tint90[i]*0.9));
}
转换完成后设置html中css的原生变量。
for(let key in theme_obj){
document.documentElement.style.setProperty('--'+key, 'rgba('+theme_obj[key].join()+')');
}
渲染结果为:
html {
--theme: rgba(215,29,17,1);
--fade90: rgba(215,29,17,0.9);
--fade20: rgba(215,29,17,0.2);
--shade5: rgba(205,28,17,1);
--tint20: rgba(223,75,65,1);
--tint80: rgba(247,210,208,1);
--tint90: rgba(251,233,232,1);
}
得到了相应得变量后,利用正则表达式替换iview.css得颜色值。
//把iview主题色#2d8cf0与之混合后的颜色值放到对象里
var theme = {
theme:'#2d8cf0',
fade90: 'rgba(45,140,240,.9)',
fade20: 'rgba(45,140,240,.2)',
shade5: '#2b85e4',
tint20: '#57a3f3',
tint80: '#d5e8fc',
tint90: '#eaf4fe'
}
let data = 'iview的css';
let reg = null;
for(let key in theme){
theme[key] = theme[key].replace('(','\\(').replace(')','\\)'); //rgba颜色值的括号需要转义
let reg = new RegExp(theme[key],"g");
data = data.replace(reg,'var(--'+key+')').replace(/\f/g,'\\f'); //css中的图标'/fxxx'字样需要转义
}
完成后,将data的值复制到iview.theme.css中,放到public下,在public下的index.html中引入这个样式。修改vue.config.js文件,添加
configureWebpack:{
externals: {
vue: 'Vue',//不打包vue
'view-design': 'iview' //不打包iview
}
},
思路总结
- 使用css的原生变量,js可以随意修改
- iview必须要手动引入,主要是css部分,不经过打包编译
- 利用主题颜色及混合后的颜色替换掉iview.css中的颜色值,替换成css变量。
如何更加灵活
现在的想法就是:可以用js实现颜色的rgba与16进制相互转换,electron可以使用node的模块,意味着也可以读写文件。 实现转换后,调用fs模块读取iview.css的内容,正则表达式替换颜色值为css原生变量值,在写入到iview.css文件中。