不重复颜色生成器的JavaScript实现

不重复颜色生成器的JavaScript实现

适用场景

最近在工作中遇到一个比较恶心的需求,产品希望能用不同颜色的点标注不同种类的数据,但是数据的种类多达百种以上。
首先,颜色的种类数量上是有最大值的(会很大,但不超过1万种)。其次,要求颜色在任何情况下都不能重复,而且色块上还要清晰地显示数字序号。

效果展示

下图为颜色生成器生成最多200种颜色的运行示例效果。
在这里插入图片描述

解决思路

总所周知,在网络上用得最多的颜色模型是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种颜色。

  1. 每份颜色的颜色值(perVal)是:16777216 / 200 = 83886
  2. 要取第3种颜色的时候:val = perVal * (3-1) = 83886 * 2 = 167772
  3. 将颜色值转成16进制并补零: 167772= 0x28F5C = #028F5C

这样每次只要传的值一样,取出来的颜色也是一样的,而且不同的传值生成的颜色肯定不一样。

对比色

要求中还提到在色块上要清晰地显示序号文字,所以这里需要计算一次对比色。所谓对比色即色相环上相距120度到180度之间的两种颜色,称为对比色。对比色的颜色区别非常明显,所以不会出现文字色和背景色相近看不清的情况。
在这里插入图片描述
查找生成对比色的算法确实比较困难,毕竟我不是相关领域的专家,但是尝试了多种方案之后,有一种方案的效果是比较好的,这里分享给大家。
将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>
    <script src="ColorMaker.js"></script>
    <script type="text/javascript">
        window.onload = function(){
            var cnt = 200;
            var html = '';
            var myColor = null;
            // 指定最大多少种颜色
            ColorMaker.TotalColors = cnt;
            for(var i = 0;i < 200;i++){
                myColor = ColorMaker.GetColor(i);
                html += '<div class="colorBlock" style="background:#'+myColor.Color+';color:#'+myColor.TextColor+'">'+i+'</div>'
            }
            document.getElementById('div_colors').innerHTML = html;
        }
    </script>
</head>
<body>
    <div id='div_colors'></div>
</body>
</html>

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值