百度前端技术学院是一个为大学生创办的免费的前端技术实践、分享、交流平台。由百度校园招聘组、百度校园品牌部、百度前端技术部以及多个百度的前端团队联合创办。学院组织了一批百度在职工程师,精心编写了数十个实践编码任务,将技术知识点系统有机地串联在各个充满趣味与挑战的任务中,同学们通过实际地编码练习来掌握知识,再辅以互相评价、学习笔记等方式,加深对于学习内容的理解。在过去的三年中,百度前端技术学院累积吸引了上万名同学参加,并且有数十名同学在学习后,顺利加入了百度,成为了百度的前端工程师。

色彩选择器之制作思路

作者张博文课程UI组件之色彩选择器2605次浏览122017-02-26 21:20

做这个选择器之前,我想大概需要想清楚这么几个问题。

1.使用什么技术进行制作

可选方案如下
(1)完全使用canvas绘制
优点:比起使用css渐变,canvas绘制起来更加容易控制,且渲染效率高,也更方便交互。若使用div渐变色,交互与取色便变得有 些麻烦了。
(2)结构完全使用div标签,色板使用css渐变
优点:结构易组织,样式易书写,且能做到结构、样式、行为分离。
(3)结构完全使用div标签,色板使用小div拼出来
优点:。。。。。不可行
经过几番思量,我决定色板部分使用canvas,以达到渲染效率高、易交互、效果好的效果。文字区使用div+css+js,以达到结构、样式、行为分离且易交互的效果。

2.DOM结构

DOM结构分为三部分:左上、右上、下,整体结构与要求中给出的demo图相同
左上部分为canvas画布,负责画出取色器的色板及色带。
右上部分为颜色输入部分,能分别输入及显示RGB及HSL六个值,也可通过上下箭头调整
下方为颜色输出部分,显示三种格式的颜色,并将自己的字体颜色改变为该颜色

3.三种颜色的相互转换

在制作的过程中,三种颜色需要相互转换,我用到的转换有:
(1)RGB 转 HSL
(2)HSL 转 RGB
(3)RGB 转 CSS Color
分析过程:因右侧输入框存在RGB及HSL值,所以两者必须可以进行相互转换。
因要求输出CSS Color,RGB转换为CSS Color也是必须的,RGB转CSS Color规则也非常简单:R、G、B的取值分别为(0~255),分别转换为16进制拼接起来即为CSS Color,如rgb(15,31,17)=>#0f1f11
因HSL转CSS Color比较麻烦,所以当输入HSL值是,我是将其转换为RGB再转换为CSS Color
对于RGB与HSL的相互转换,算法有点复杂,我参照了以下链接,写的很详细。http://blog.csdn.net/jiangxinyu/article/details/8000999
以下为RGB转HSL

function rgbChangeIntohsl(rgb) {
        var r = rgb[0] / 255,
            g = rgb[1] / 255,
            b = rgb[2] / 255;
        var min = Math.min.apply(Array, [r, g, b]),
            max = Math.max.apply(Array, [r, g, b]);
        var h, s, l;
        if (max == min) {
            h = 0;
        }
        else if (max == r && g >= b) {
            h = 60 * (g - b) / (max - min);
        }
        else if (max == r && g < b) {
            h = 60 * (g - b) / (max - min) + 360;
        }
        else if (max == g) {
            h = 60 * (b - r) / (max - min) + 120;
        }
        else if (max == b) {
            h = 60 * (r - g) / (max - min) + 240;
        }
        l = (max + min) / 2;
        if (l == 0 || max == min) {
            s = 0;
        }
        else if (l > 0 && l <= 0.5) {
            s = (max - min) / (2 * l);
        }
        else if (l > 0.5) {
            s = (max - min) / (2 - 2 * l);
        }

        return [Math.round(h), Math.round(s * 100) / 100, Math.round(l * 100) / 100];

    }
以下为HSL转RGB
function hslChangeIntorgb(hsl) {
        var h = hsl[0] - 0,
            s = hsl[1] - 0,
            l = hsl[2] - 0;

        var r, g, b;
        if (s == 0) {
            r = g = b = l;
        }
        else {
            var p, q, k;
            if (l < 0.5) {
                q = l * (1 + s);
            }
            else if (l >= 0.5) {
                q = l + s - (l * s);
            }
            p = 2 * l - q;
            k = h / 360;

            r = singleColorCalculation(k + 1 / 3);
            g = singleColorCalculation(k);
            b = singleColorCalculation(k - 1 / 3);

        }

        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];

        function singleColorCalculation(k) {

            var color;

            if (k < 0) {
                k += 1;
            }
            if (k > 1) {
                k -= 1;
            }

            if (k * 6 < 1) {
                color = p + ((q - p) * 6 * k);
            }
            else if (k * 6 >= 1 && k < 0.5) {
                color = q;
            }
            else if (k >= 0.5 && 3 * k < 2) {
                color = p + ((q - p) * 6 * (2 / 3 - k));
            }
            else {
                color = p;
            }

            return color;

        }

    }

3.联动规律

(1)当鼠标在色板上选取,右侧颜色输入区域的显示数值、下方颜色输出区的内容及字体颜色,都应发生相应的改变。
(2)当鼠标在色带上选取,色板颜色应发生改变,选取的颜色亦发生改变,同样引起(1)中所述改变。
(3)当在右侧颜色输入区域输入颜色或通过箭头调整颜色时,色板颜色应发生改变,同样引起(1)中所述改变。

4.细节明确

(1)RGB和HSL中各参数的取值范围。R、G、B的取值为[0~255],H的取值为[0~360),S、L的取值为[0~1]
(2)每个参数通过箭头微调时改变值,我决定R、G、B、H每点击一下箭头变会加或减1且精度为整数,而S与L每点击一次会增加或减少0.01且精度为两位小数。
(3)出现精度不同时的处理:当R、G、B、H出现小数时,若通过箭头微调,若为增加,便舍弃小数部分,若为减少,小数便向上进一位。若通过手动输入,在input失焦时,小数部分采取四舍五入。H、L同理。
(4)碰撞检测:当鼠标点击色板及色带时,应绘制出小圆圈表示取色位置,而当点击角落时,会出现圆圈超过边界的情况,这时应进行碰撞检测,使圆圈始终在边界之内
(5)用户体验的优化:当鼠标点击时,为取色点行为。但是用户的习惯为点下鼠标然后拖动鼠标,直到找到合适的颜色才会松开鼠标,所以取色点的事件不应为click,而应为mousedown和mousemove。我设置了一个开关flag,当mousedown时,将开关设置为true且触发事件进行取色点,当mousemove时,若flag为true,同样触发事件进行取色点,当mouseup,关闭开关,flag为false。

this.canvas.addEventListener('mousedown', function (e) {
                var e = e || window.event;
                isDown = true;
                that.checkChangePoint(e);
            });
            this.canvas.addEventListener('mousemove', function (e) {
                var e = e || window.event;
                if (isDown) {
                    that.checkChangePoint(e);
                }
            });
            this.canvas.addEventListener('mouseup', function () {
                isDown = false;
            });

(6)对canvas进行事件监听时进行判断,点击点是否位于色板及色带内,并分别触发不同事件

/**
         * 检测点是否位于对应色板矩形路径内
         * @param point  期望格式{x:x,y:y}
         * @returns {boolean} 如果位于路径内,返回true,否则返回false
         */
        isPointInBoardPath: function (point) {
            var context = this.context;
            context.beginPath();
            context.rect(BOARDRECT.x + 1, BOARDRECT.y + 1, BOARDRECT.len - 2, BOARDRECT.hei - 3);
            if (context.isPointInPath(point.x, point.y)) {
                //点在路径中
                return true;
            }
            else {
                return false;
            }
        },
        /**
         * 检测点是否位于对应色带矩形路径内
         * @param point 期望格式{x:x,y:y}
         * @returns {boolean} 如果位于路径内,返回true,否则返回false
         */
        isPointInBeltPath: function (point) {
            var context = this.context;
            context.beginPath();
            context.rect(BELTRECT.x + 1, BELTRECT.y + 1, BELTRECT.len - 2, BELTRECT.hei - 3);
            if (context.isPointInPath(point.x, point.y)) {
                //点在路径中
                return true;
            }
            else {
                return false;
            }
        },

5.色带及色板的绘制

(1)绘制色板时我采取的canvas的线性渐变(因为demo图上是线性渐变,径向渐变实在不知道该做成什么样),左上角为rgb(255,255,255),右下角为rgb(0,0,0),中间颜色为根据色带区域选出的颜色,作为参数传入。
(2)色带渐变为从上到下线性渐变
(3)我按照demo图制作的时候,发现按照demo种的做法,无法取到所有256256256种颜色,或者是我没好的处理办法,欢迎大家提出宝贵的思路和意见。

var cardGredient = context.createLinearGradient(0, 0, 500, 500);
            cardGredient.addColorStop(0, 'rgb(255,255,255)');
            cardGredient.addColorStop(0.5, this.midColor);
            cardGredient.addColorStop(1, 'rgb(0,0,0)');
            context.fillStyle = cardGredient;
            context.fillRect(BOARDRECT.x + 1, BOARDRECT.y + 1, BOARDRECT.len - 2, BOARDRECT.hei - 2);

然后,就可以愉快地编码了。

0条评论