适用场景
最近在工作中遇到一个比较恶心的需求,产品希望能用不同颜色的点标注不同种类的数据,但是数据的种类多达百种以上。
首先,颜色的种类数量上是有最大值的(会很大,但不超过1万种)。其次,要求颜色在任何情况下都不能重复,而且色块上还要清晰地显示数字序号。
效果展示
下图为颜色生成器生成最多200种颜色的运行示例效果。
![](https://img.haomeiwen.com/i5077260/d0a1e7606ecc52d8.png)
解决思路
总所周知,在网络上用得最多的颜色模型是RGB,即颜色可以分为Red(红)、Green(绿)、Bule(蓝)三个维度。红绿蓝根据不同的深浅程度可以混合成为任意颜色。
按照一般的思路,首先想到生成RGB三个随机数,然后查重,无重复后组合成颜色。但是这样效率太低、生成的时间不可控、而且多次执行的效果不一样,即还要用一个字典专门存储不同数据类型对应的颜色。
因此,这里不采用随机数的方法。
颜色生成
正如上文所说,颜色模型RGB是由R(0-255), G(0-255), B(0-255)三个数字组成,即最多可以组成 256 X 256 X 256 = 16777216 (0xFFFFFF) 种颜色。我们可以把0xFFFFFF平均分成 x 份,每份的颜色值是 16777216 / x。要取第i份的时候,用i * (16777216 / x),把计算出来的值转成16进制,前面补0补够6位即可。
假设这里要取最多200种颜色。
每份颜色的颜色值(perVal)是:16777216 / 200 = 83886
要取第3种颜色的时候:val = perVal * (3-1) = 83886 * 2 = 167772
将颜色值转成16进制并补零: 167772= 0x28F5C = #028F5C
这样每次只要传的值一样,取出来的颜色也是一样的,而且不同的传值生成的颜色肯定不一样。
对比色
要求中还提到在色块上要清晰地显示序号文字,所以这里需要计算一次对比色。所谓对比色即色相环上相距120度到180度之间的两种颜色,称为对比色。对比色的颜色区别非常明显,所以不会出现文字色和背景色相近看不清的情况。
![](https://img.haomeiwen.com/i5077260/56fea59a33ad25b9.png)
查找生成对比色的算法确实比较困难,毕竟我不是相关领域的专家,但是尝试了多种方案之后,有一种方案的效果是比较好的,这里分享给大家。
将RGB颜色模型转换成HSL(色相、饱和度、亮度)颜色模型,然后将HSL中的L即亮度的差异变大,这里是将它+0.5,因为L的取值是0-1,所以还要对1取模。最后将生成的颜色转回RGB显示。
至于RGB和HSL互转的算法,不是专业人士的我也不懂,只能引用别人的实现。
完整代码
ColorMaker.js
function MyColor() {
this.TextColor = 'FFFFFF';
this.Color = 'FF0000';
}
var ColorMaker = {
// 最大支持颜色种类数
TotalColors: 300,
// 获取颜色 seed:颜色种子(任意int)
GetColor: (seed = 0) => {
var ret = new MyColor();
// 计算对应下标
var idx = seed % ColorMaker.TotalColors;
// 计算颜色
var colorVal = ColorMaker._CalColor(idx);
// 转成RGB 16进制字符串
ret.Color = colorVal.toString(16).padStart(6, '0');
// 计算互补色
ret.TextColor = ColorMaker._CalTextColor(ret.Color);
return ret;
},
_CalColor: (idx = 0) => {
// 默认返回红色
var ret = 0xFF0000;
// RGB的最大值
var full = 0xFFFFFF;
// 总共需要支持多少种颜色,若传0则取255
var total = ColorMaker.TotalColors > 0 ? ColorMaker.TotalColors : 0xFF;
// 将所有颜色平均分成x份
var perVal = full / total;
if (idx >= 0 && idx <= total) {
ret = perVal * idx;
}
ret = Math.round(ret);
return ret;
},
// 计算传入颜色的互补色
_CalTextColor: (input = '') => {
var R = input.substr(0, 2);
var G = input.substr(2, 2);
var B = input.substr(4, 2);
var rVal = parseInt(R, 16);
var gVal = parseInt(G, 16);
var bVal = parseInt(B, 16);
var hsl = rgbToHsl(rVal, gVal, bVal);
hsl.L = (hsl.L + 0.5) % 1.0;
var rgb = hslToRgb(hsl.H, hsl.S, hsl.L);
var ret = (rgb.R << 16) + (rgb.G << 8) + rgb.B;
return ret.toString(16).padStart(6, '0');
}
};
/**
* RGB 颜色值转换为 HSL.
* 转换公式参考自 http://en.wikipedia.org/wiki/HSL_color_space.
* r, g, 和 b 需要在 [0, 255] 范围内
* 返回的 h, s, 和 l 在 [0, 1] 之间
*
* @param Number r 红色色值
* @param Number g 绿色色值
* @param Number b 蓝色色值
* @return Array HSL各值数组
*/
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { H: h, S: s, L: l };
}
/**
* HSL颜色值转换为RGB.
* 换算公式改编自 http://en.wikipedia.org/wiki/HSL_color_space.
* h, s, 和 l 设定在 [0, 1] 之间
* 返回的 r, g, 和 b 在 [0, 255]之间
*
* @param Number h 色相
* @param Number s 饱和度
* @param Number l 亮度
* @return Array RGB色值数值
*/
function hslToRgb(h, s, l) {
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return { R: Math.round(r * 255), G: Math.round(g * 255), B: Math.round(b * 255) };
}
引用示例 Test.html
<!DOCTYPE html>
<html>
<head>
<title>测试页面</title>
<style>
.colorBlock{
width:50px;
float:left;
line-height:50px;
}
</style>
</head>
<body>
<div id='div_colors'></div>
<button onclick="add()">加一组</button>
<script src="index.js"></script>
<script type="text/javascript">
var cnt = 200;
var html = '';
var myColor = null;
var indexnum = 1;
// 指定最大多少种颜色
ColorMaker.TotalColors = cnt;
function load(){
for(var i = (indexnum -1) * 50 ;i < 50 * indexnum;i++){
myColor = ColorMaker.GetColor(i);
html += '<div class="colorBlock" style="background:#'+myColor.Color+';color:#'+myColor.TextColor+'">'+i+'</div>'
}
document.getElementById('div_colors').innerHTML = html;
}
load();
function add(){
indexnum++;
load();
}
</script>
</body>
</html>
网友评论