前言
今天来讲一下如何用javascript写一个色彩选择器。上一篇写到用JS实现RGB,HSL,HSV之间的相互转换,而那一篇写的代码呢,就是为写这个色彩选择器而准备的。
先放上效果图,代码是基于jquery的,所以要引用上哟。
或者戳这里看效果,源码在这里
功能需求
- 可以通过点击左侧色彩区域选择色彩,在右侧的输入框中会显示对应的色值,包括RGB,HSL和HSV的值
- 可以通过调节右侧输入框的各数值,来查看对应的颜色
- 可以通过在中间的色带上点击来确定大致的颜色区域
- 提供设定颜色值的接口,指定具体颜色,左侧色区和右侧输入框显示对应数值
- 提供获取颜色值的接口,可获取色彩选择器选中的颜色值,可返回CSS color、RGB、HSL、HSB四种格式的色值
总体思路
左边色彩区域
通过效果图,你可以试着多取色,看看数值的一些规律。
从HSV数值上来看(如有不了解,请戳上篇)。
- 左边取色渐变色块的色相是一直保持不变的
- 从左到右,饱和度是从低到高的趋势
- 从上到下,色彩明度是从高到低的。
- 即从左到右,S值从0%到100%,从上到下,V值从100%到0%。
从RGB模型角度来说
- 点击最上边一排水平来看,会发现最左上角的数值永远都是rgb(255,255,255),即是白色(用上HSV数值也可解释,饱和度最低,明度最高,即是白色)。
- 而最右上方的数值则与我们中间的色带上的颜色是一致的。即该色相下,饱和度最高,明度最高的颜色。
- 从最左边垂直而下看,会发现RGB的值由(255,255,255)到(0,0,0)变化,即从白到黑。
中间色带
- 至于中间的色带,是用来选择色彩的色相的。从上到下,H值从0-360渐变。
- 从HSV值来看,会发现S值一直为100%,V值也为100%;从上面即可解释这一原因。
- 而对于HSL值,S值一直为100%,L值也为50%;这也是两者本身定义不同产生的。
知道这些之后,就大概知道如何下手了。
关键代码
HTML
额…代码写的比较粗糙,html和css都写的比较死,不灵活,对于写成一个真正的插件而言,还需要加入很多样式方面的考虑,笔者比较懒,这先这样吧。整体html如下
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 44 45 46 47 48 49 50 51 52 53 54
| <div class="colorpicker"> <div class="colorbox"> <canvas id="canvas" height="300" width="300"></canvas> <div id="colorpicker-circle"></div> </div> <div class="colorslide" id="colorpicker-slide"> <div id="colorslide-circle"></div> </div> <div class="colorcontent"> <div class="pickerbox" id="pickercolor"></div> <hr> <span>hex:</span><input type="text" id="hex"> <hr> <div class="rgbbox"> <ul> <li>R<input type="number" name="R" id="colorpicker-rgbr" min="0" max="255"></li> <li>G<input type="number" name="G" id="colorpicker-rgbg" min="0" max="255"></li> <li>B<input type="number" name="B" id="colorpicker-rgbb" min="0" max="255"></li> </ul> </div> <hr> <div class="hslvbox"> <table class="hslbox"> <tr> <td>H</td> <td><input type="number" name="H" id="colorpicker-hslh" step="1" min="0" max="360"></td> </tr> <tr> <td>S</td> <td><input type="number" name="S" id="colorpicker-hsls" step="1" min="0" max="100">%</td> </tr> <tr> <td>L</td> <td><input type="number" name="L" id="colorpicker-hsll" step="1" min="0" max="100">%</td> </tr> </table>
<table class="hsvbox"> <tr> <td>H</td> <td><input type="number" name="HH" id="colorpicker-hsvh" step="1" min="0" max="360"></td> </tr> <tr> <td>S</td> <td><input type="number" name="SS" id="colorpicker-hsvs" step="1" min="0" max="100">%</td> </tr> <tr> <td>V</td> <td><input type="number" name="V" id="colorpicker-hsvv" step="1" min="0" max="100">%</td> </tr> </table> </div> </div> </div>
|
新建colorPicker对象
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
| var colorPicker= function(id,config){ this.element=$("#"+id)[0]; this.jqelement=$("#"+id); this.slide=$("#colorpicker-slide")[0]; this.jqslide=$("#colorpicker-slide"); this.width = this.element.width || 300; this.height = this.element.height ||300; this.pickercircle=$("#colorpicker-circle"); this.context = this.element.getContext("2d"); this._init(); } colorPicker.prototype = { _init:function(){ this.canvasX=0; this.canvasY=0; this.hsl=[0,100,50]; this.rgb=[255,0,0]; this.hsv=[0,100,100]; this.hex="#ffffff"; this._drawbg(); this._pickcolor(); this._slidecolor(); this._updatergb(); this._updatehslv(); this._contentchange(); } }
|
色彩转换函数
不多说了,看上一篇或者源码
- rgbtohsl(r,g,b)
- rgbtohsv(r,g,b)
- rgbtohex(r,g,b)
- hsltorgb(h,s,l)
- hsvtorgb(h,s,v)
- hextorgb(hex)
绘制左边取色渐变区块
左边的渐变区块我是采用canvas绘图的方式来绘制渐变的效果。用canvas有什么好处呢,canvas可以确定某个画布位置的颜色,返回该点的rgb颜色,这样省事不少。
【注意】这里绘制渐变时要注意需要绘制两个渐变,一个是水平的渐变,一个是垂直的渐变,垂直的渐变是对rgb(0,0,0)的透明度进行改变,一定是透明的改变,因为只有这样,才能叠加上颜色,否则便是灰色的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| _drawbg:function(color){ var x1 = 0; var y1 = 0; var linear_gradient_hori = this.context.createLinearGradient(x1, y1, this.width, 0); linear_gradient_hori.addColorStop(0, 'rgba(255,255,255,1)'); linear_gradient_hori.addColorStop(1,color?color:'rgba(255,0,0,1)'); this.context.fillStyle=linear_gradient_hori; this.context.fillRect(0, 0, this.width, this.height); var linear_gradient_ver = this.context.createLinearGradient(x1, y1, 0, this.height); linear_gradient_ver.addColorStop(0, 'rgba(0,0,0,0)'); linear_gradient_ver.addColorStop(1,'rgba(0,0,0,1)'); this.context.fillStyle=linear_gradient_ver; this.context.fillRect(0, 0, this.width, this.height); }
|
在前期看别人写的插件时,发现很多都是用了一个这样一个灰色的渐变的透明背景图片,然后在父级的div设置一个背景颜色,该背景颜色就是来自中间色带,两者叠加来形成渐变区域,其实细细想来与上面用的原理都是一样的。
戳这里是别人写的一个色彩选择器插件,它就是用的这个方法。
绘制中间色带
用css的渐变来绘制,这里方法有很多,就不多说了。
1 2 3 4 5 6 7
| .colorslide{ height: 300px; width: 28px; position: relative; background: linear-gradient(to bottom, hsla(0,100%,50%,1) 10%,hsla(36,100%,50%,1) 20%,hsla(72,100%,50%,1) 30%,hsla(108,100%,50%,1) 40%,hsla(180,100%,50%,1) 50%,hsla(216,100%,50%,1) 60%,hsla(252,100%,50%,1) 70%,hsla(288,100%,50%,1) 80%,hsla(324,100%,50%,1) 90%, hsla(360,100%,50%,1) 100%); overflow: hidden; }
|
点击左边区域取色
在左边取色区域点击之后,首先获取点击的位置,通过canvas的getImageData方法获得该位置的rgb颜色,进行转换等到相应hex,hsl,hsv的值,更新表达的值,把取色小圆圈的位置设置到该点位置上去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| _pickcolor:function(){ var that=this; that.jqelement.bind("click",function(e){ var canvasrect=that.element.getBoundingClientRect(); that.canvasX = e.clientX-canvasrect.left*(that.width/canvasrect.width); that.canvasY = e.clientY-canvasrect.top*(that.height/canvasrect.height); var imageData = that.context.getImageData(that.canvasX, that.canvasY, 1, 1); var pixel = imageData.data; that.rgb=pixel.slice(0,3); that.hex=rgbtohex(pixel[0],pixel[1],pixel[2]); that.hsl=rgbtohsl(pixel[0],pixel[1],pixel[2]); that.hsv=rgbtohsv(pixel[0],pixel[1],pixel[2]); that._updatergb(); that._updatehslv(); that.pickercircle.show(); that.pickercircle.css({"top":that.canvasY-5,"left":that.canvasX-5}); }) }
|
中间色带点击取色相
点击之后,由于中间色带的色相是递增的,获取取色相小圆圈相对于整个中间色带的高度即可获取色相,此时需要重新绘制左边的渐变区域,由于这里只变化了h值,即可知hsl值,根据hsl值获得其他格式的颜色值更新即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| _slidecolor:function(){ var that=this; that.jqslide.bind("click",function(e){ if(e.target.id=="colorpicker-slide"){ $("#colorslide-circle").css({"top":e.offsetY-8}); that.hsl[0]=that.hsv[0]=Math.round(e.offsetY/that.height*360); var hsl=that.hsl[0]+",100%,50%,1"; that._drawbg("hsla("+hsl+")"); that.rgb=hsltorgb(that.hsl[0],that.hsl[1],that.hsl[2]); that._updatergb(); $("#colorpicker-hslh").val(that.hsl[0]); $("#colorpicker-hsvh").val(that.hsv[0]); } }) }
|
更新颜色表单的数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| _updatergb:function(){ $("#colorpicker-rgbr").val(this.rgb[0]); $("#colorpicker-rgbg").val(this.rgb[1]); $("#colorpicker-rgbb").val(this.rgb[2]); this.hex=rgbtohex(this.rgb[0],this.rgb[1],this.rgb[2]); $("#hex").val(this.hex); $("#pickercolor").css({"background-color":this.hex}); }
_updatehslv:function(){ this.hsv=rgbtohsv(this.rgb[0],this.rgb[1],this.rgb[2]); $("#colorpicker-hsvh").val(this.hsv[0]); $("#colorpicker-hsvs").val(this.hsv[1]); $("#colorpicker-hsvv").val(this.hsv[2]); $("#colorpicker-hslh").val(this.hsl[0]); $("#colorpicker-hsls").val(this.hsl[1]); $("#colorpicker-hsll").val(this.hsl[2]); }
|
监听表单的数值变化
这里提一下,改变了某一表单数值后,其他格式的颜色数值相对应改变,还需要改变左边区域取色小圆圈的位置,让其跳转到对一个颜色的像素上,中间色带的取色相小圆圈也需要转到对应的色相颜色上。
思路是:表单改变后,得到所有格式的颜色数值,根据h值来使得中间色带的取色相小圆圈变化到对应的色相颜色上。而根据hsv中的s和v可以获取左边区域取色小圆圈应该在位置(渐变的原理)
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| _contentchange(){ var that=this; $("#hex").bind("change",function(e){ var hex=$("#hex").val(); var rgb; if(rgb=hextorgb(hex)){ that.hex=hex; that.rgb=rgb; that.hsl=rgbtohsl(rgb[0],rgb[1],rgb[2]); that.hsv=rgbtohsv(that.rgb[0],that.rgb[1],that.rgb[2]); that._updatechange(); } }) $(".rgbbox input").each(function(index,obj){ $(obj).bind("change",function(e){ var value=parseInt($(obj).val()); if(value>0 && value<=255){ that.rgb[index]=value; that.hex=rgbtohex(that.rgb[0],that.rgb[1],that.rgb[2]); that.hsl=rgbtohsl(that.rgb[0],that.rgb[1],that.rgb[2]); that.hsv=rgbtohsv(that.rgb[0],that.rgb[1],that.rgb[2]); that._updatechange(); } }) }) $(".hslbox input").each(function(index,obj){ $(obj).bind("change",function(e){ var value=parseInt($(obj).val()); if((index==0 && value<=360 && value>=0)||(value<=100 && value>=0)){ that.hsl[index]=value; that.rgb=hsltorgb(that.hsl[0],that.hsl[1],that.hsl[2]); that.hsv=rgbtohsv(that.rgb[0],that.rgb[1],that.rgb[2]); that.hex=rgbtohex(that.rgb[0],that.rgb[1],that.rgb[2]); that._updatechange(); } }) }) $(".hsvbox input").each(function(index,obj){ $(obj).bind("change",function(e){ var value=parseInt($(obj).val()); if((index==0 && value<=360 && value>=0)||(value<=100 && value>=0)){ that.hsv[index]=value; that.rgb=hsvtorgb(that.hsv[0],that.hsv[1],that.hsv[2]); that.hsl=rgbtohsl(that.rgb[0],that.rgb[1],that.rgb[2]); that.hex=rgbtohex(that.rgb[0],that.rgb[1],that.rgb[2]); that._updatechange(); } }) }) },
_updatechange(){ this._updatergb(); this._updatehslv(); var offestheight=this.hsl[0]/360*this.height; $("#colorslide-circle").show(); this.pickercircle.show(); $("#colorslide-circle").css({"top":offestheight-8}); var hsl=this.hsl[0]+",100%,50%,1"; this._drawbg("hsla("+hsl+")"); this.hsv=rgbtohsv(this.rgb[0],this.rgb[1],this.rgb[2]); var canvaswidth=this.hsv[1]/100*this.width; var canvasheight=(1-this.hsv[2]/100)*this.height; this.pickercircle.css({"top":canvasheight-5,"left":canvaswidth-5}); } };
|
代码写的有点乱,还有很多需要改进的地方,暂时就先这样吧。