less换肤功能实践

在我司的后台管理组件库pagurainrsuite中要求实现换肤功能,以下是我们对这一功能实践的记录及过程。

前提

为了统一各产品的视觉和操作体验,我们建立了UI规范体系,方便创建项目也方便统一管理。
以扁平化和简洁为主旨的设计风格,使用块面来进行布局,用线条来表达各个控件,使功能庞杂的系统 既直观又条理清晰,让使用者一目了然。

主色及色阶

系统共用的组件颜色和变化形式是统一的,在此基础上,各系统又有自己的主色调。

每套系统都会有一个主色,并在主色的基础上,在同一色调中扩充成一组完整的可复用的色彩体系,丰富系统配色。

如下图所示,每组色彩是按一定的规律在明度和饱和度上选取得出的。

初步实现

按照上面的实现,我们的多套皮肤就是先定义一个主色 @H500,然后使用 @H500 这个颜色计算出其他的颜色,就搞定了。每次在不同的主题就换一套颜色。当然最终还是要生成多套颜色的css
下面就按照这个思路开始吧。首先,我们按照设计师的要求定义一组颜色,如下:

1
2
3
4
5
6
7
8
9
10
@H050:#e8f4ee;
@H100:#bbdfcb;
@H200:#8dcaa8;
@H300:#67b88b;
@H400:#41a66e;
@H500:#1b9451;
@H600:#188247;
@H700:#146f3d;
@H800:#115d33;
@H900:#0e4a29;

这组颜色的计算规则如下:
1
2
3
4
5
6
7
8
9
10
@H050:darken(@H500,90%);
@H100:darken(@H500,70%);
@H200:darken(@H500,50%);
@H300:darken(@H500,33.3%);
@H400:darken(@H500,16.6%);
@H500:#1b9451;
@H600:lighten(@H500,12.5%);
@H700:lighten(@H500,25%);
@H800:lighten(@H500,37.5%);
@H900:lighten(@H500,50%);

这猛一看挺简单啊,颜色的计算规则也有了,less也有相应的函数,这不很简单么。直接一算就出来了。
结果呢:

先用个绿色(#1b9451)试试

换个蓝色(#b3d1ff)呢

结果发现,如果颜色太深,那么darken后的颜色就是一堆黑色,如果颜色偏浅,那lighten后的颜色就是一堆白色。所以显然,使用less自带的颜色函数式不行的。那怎么办呢?
最简单的方法就是,现在外面算好了再复制进来,每次都复制10个颜色。计算颜色的方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* Color utility functions
* Source: http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
* Github: https://github.com/mbitson/mcg
*/
function shadeColor(color, percent) {
var f = parseInt(color.slice(1), 16),
t = percent < 0 ? 0 : 255,
p = percent < 0 ? percent * -1 : percent,
R = f >> 16,
G = f >> 8 & 0x00FF,
B = f & 0x0000FF;
return "#" + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1);
}

为此我们还特意做了一个页面来方便使用,页面链接
这样一来最起码的颜色就有了,我们只需先用这些变量生成一套基础的主题base.less,然后分别创建多个主题文件的green.lessblue.less,再分别覆盖这10个颜色变量,然后
1
@import "base"; //按照less 语法import less 可以省略文件ext

最终就可以得到多套css了。

改进

一次只传一个颜色

仅仅是那样,用肯定是没问题了,但是每次使用都要去生成配色的页面生成颜色,用着着实不爽。于是乎就想着能不能够有什么方法,自己去定义一个方法在less里直接调用。最终发现grunt-contrib-less这个插件有一个customFunctions方法,可以实现自定义方法,所以改进了一下。而且可以覆盖原有的变量,所以改进了一下,得到了如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//生成主题
grunt.registerTask('theme', 'Generate theme', function () {
Object.keys(themes).forEach((name) => {
const color = themes[name];
const taskName = `theme-${name}`;
const outputName = `src/resources/css/themes-${name}.css`;
grunt.config.merge({
less: {
[taskName]: {
options: {
customFunctions: {
pallet
},
modifyVars: {
'base-color': color
}
},
files: {
[outputName]: 'src/resources/less/theme/base.less'
}
}
}
});
grunt.task.run(`less:${taskName}`);
});
});

源代码地址;

扔掉grunt

但是这个时候问题又来了,因为这样做的话,就只能用grunt进行构建了,gulp构建或者webpack打包肯定不行啊(没有pallet方法),那怎么办呢。一个偶然发现使用less中的转义语法 ~ 居然可以执行js。简直了。然后经过测试使用这种hack方法可以成功的定义function,就解决了一切问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.colorPaletteMixin {
@functions: ~`(function () {
/*
* Color utility functions
* Source: http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
* Github: https://github.com/mbitson/mcg
*/
var shadeColor = function (color, percent) {
var f = parseInt(color.slice(1), 16),
t = percent < 0 ? 0 : 255,
p = percent < 0 ? percent * -1 : percent,
R = f >> 16,
G = f >> 8 & 0x00FF,
B = f & 0x0000FF;
return "#" + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1);
}

/**
* 计算系统颜色
* @param color
* @param percent (正值表示lighter,负值表示darken)
*/
this.pallet = function (color, percent) {
var colorConfig = {
'50': 0.9,
'100': 0.7,
'200': 0.5,
'300': 0.333,
'400': 0.166,
'500': 0,
'600': -0.125,
'700': -0.25,
'800': -0.375,
'900': -0.5
};
percent = colorConfig[percent] === undefined ? percent : colorConfig[percent] ;
color = color.toLowerCase();
return shadeColor(color, percent);
}
})()`;
}
// It is hacky way to make this function will be compiled preferentially by less
.colorPaletteMixin();

小结

最后我只想说,写好文档太重要了,这里各种的坑,简直是踩也踩不完。说多了都是泪。安利一下我司的react组件库rsuite。l