【canvas系列】用canvas实现一个colorpicker(类似PS的颜色选择器)

来源:互联网 发布:php干什么用的 编辑:程序博客网 时间:2024/05/17 12:21

每个浏览器都有自己的特点,比如今天要做的colorpicker就是,一千个浏览器,一千个哈姆雷特,一千个colorpicker。今天canvas系列就用canvas做一个colorpicker。

 **********************************************************************

效果图和demo

突然翻到了之前用js和dom写的一个colorpicker,比较挫,扔张图就好(old)

这个真的很挫,性能很差,因为每一个可选的颜色值都是一个dom,如果要实现256*256,那浏览器就爆了~~~~~

 

好,回到今天的demo(new)

demo链接: https://win7killer.github.io/can_ps/src/demo/color_picker.html

 

没错,就是照着PS的颜色选择器的样子仿的。

**********************************************************************

实现

 

首先我们来看效果图分析怎么做:

1.左侧colorbar

    左侧提供一系列过渡色,不难看出,这个是“红黄绿青蓝紫”这六种颜色,然后加以过渡色处理来的。最后紫色还要过渡回到红色。

 

另外换成环状的可能更加好识别,如下图:

那么,我们就可以用canvas的过渡色来实现左侧这个区域,

代码如下:

 1 function colorBar() { 2     var gradientBar = ctx.createLinearGradient(0, 0, 0, width); 3     gradientBar.addColorStop(0, '#f00'); 4     gradientBar.addColorStop(1 / 6, '#f0f'); 5     gradientBar.addColorStop(2 / 6, '#00f'); 6     gradientBar.addColorStop(3 / 6, '#0ff'); 7     gradientBar.addColorStop(4 / 6, '#0f0'); 8     gradientBar.addColorStop(5 / 6, '#ff0'); 9     gradientBar.addColorStop(1, '#f00');10 11     ctx.fillStyle = gradientBar;12     ctx.fillRect(0, 0, 20, width);13 }

这里涉及到canvas的fillStyle或者strokenStyle的填充对象,可以使用过渡色对象(自己瞎叫的名字),了解更多可以去w3cschool。

 

 2.中间颜色区

 

中间这块乍看很简单,再看有点蒙bi,三看才搞清楚怎么搞。

乍看:其实就是左侧选中的那个颜色(比如A),然后进行过渡处理,不还是过渡么。

再看:恩,颜色,然后黑色,白色,三种颜色三个角怎么过渡~~~~(如果有快捷的过渡实现方式请留言告知我,THX)。

三看:那么,拆解一下,比如红色到白色过渡,然后加一层黑色到透明过渡?是滴,就是这么个方案。(我自己之前弯路到了红色到黑色,白色到透明)

那么就是借助两次过渡色的填充,实现中间色块区域。

代码如下:

 1 function colorBox(color) { 2     // 底色填充,也就是(举例红色)到白色 3     var gradientBase = ctx.createLinearGradient(30, 0, width + 30, 0); 4     gradientBase.addColorStop(1, color); 5     gradientBase.addColorStop(0, 'rgba(255,255,255,1)'); 6     ctx.fillStyle = gradientBase; 7     ctx.fillRect(30, 0, width, width); 8      9     // 第二次填充,黑色到透明10     var my_gradient1 = ctx.createLinearGradient(0, 0, 0, width);11     my_gradient1.addColorStop(0, 'rgba(0,0,0,0)');12     my_gradient1.addColorStop(1, 'rgba(0,0,0,1)');13     ctx.fillStyle = my_gradient1;14     ctx.fillRect(30, 0, width, width);15 }

需要注意,第一次填充,是从横向填充,这时候中间色块的左边已经不是canvas的原点,所以加了偏移量30px

第二次填充纵向,Y轴还是0。

这个在实际应用中要注意。

 

到这里,左侧canvas绘制的东西就差不多了。

 

3. 颜色选择事件处理

首先明确交互事件:

选择左侧colorbar(比如#ff0),中间base颜色要跟着变化,右上角也要是对应颜色(#ff0)【这个时候其实也可以得到选择的颜色,可以结束交互】;

选择中间区域的颜色,左侧不变,可以获取到对应的颜色值,结束交互。

最终就是在右侧的dom区域展示所选到的颜色。

 

canvas中没有dom对象,所以鼠标点击事件要靠鼠标的位置来确定是否进行相应处理。而且我们绘制的不是path对象,也无法使用inpath之类的方法来判断。

点击事件代码:

 1 can.addEventListener('click', function(e) { 2     var ePos = { 3         x: e.offsetX || e.layerX, 4         y: e.offsetY || e.layerY 5     } 6     var rgbaStr = '#000'; 7     if (ePos.x >= 0 && ePos.x < 20 && ePos.y >= 0 && ePos.y < width) { 8         // in 9         rgbaStr = getRgbaAtPoint(ePos, 'bar');10         colorBox('rgba(' + rgbaStr + ')');11     } else if (ePos.x >= 30 && ePos.x < 30 + width && ePos.y >= 0 && ePos.y < width) {12         rgbaStr = getRgbaAtPoint(ePos, 'box');13     } else {14         return;15     }16     outColor(rgbaStr.slice(0, 3).join());17     cur.style.left = ePos.x + 'px';18     cur.style.top = ePos.y + 'px';19     cur.style.outlineColor = (rgbaStr[0] > 256 / 2 || rgbaStr[1] > 256 / 2 || rgbaStr[2] > 256 / 2) ? '#000' : '#fff';20 });

其中,getRgbaAtPoint是最终的获取颜色值的方法,需要根据不同的鼠标位置传参来决定选取左侧还是右侧图像

 

获取颜色就比较简单了,就是拿到对应区域的imageData,然后从颜色数组中获取到对应位置的颜色值即可。

做过canvas像素处理的同学会比较明白,不明白的建议先去把getImageData方法看一看,了解一下

获取颜色代码:

 1 function getRgbaAtPoint(pos, area) { 2     if (area == 'bar') { 3         var imgData = ctx.getImageData(0, 0, 20, width); 4     } else { 5         var imgData = ctx.getImageData(0, 0, can.width, can.height); 6     } 7  8     var data = imgData.data; 9     var dataIndex = (pos.y * imgData.width + pos.x) * 4;10     return [11         data[dataIndex],12         data[dataIndex + 1],13         data[dataIndex + 2],14         (data[dataIndex + 3] / 255).toFixed(2),15     ];16 }

这时候拿到的就是rgba颜色对应的值。

需要注意,最后一个数据时alpha通道,canvas的imageData里是0-255【没记错的话】,而不是我们平常用的0-1,所以要做转换

 

颜色输出&转换:

拿到颜色后就可以输出到右侧了。

右侧只是用了rgb三通道,所以取数组前三位就好。

至于hex颜色,则用rgb来转换。

转换代码如下:

 1 function rgb2hex(rgb) { 2     var aRgb = rgb instanceof Array ? rgb : (rgb.split(',') || [0, 0, 0]); 3     var temp; 4     return [ 5         (temp = Number(aRgb[0]).toString(16)).length == 1 ? ('0' + temp) : temp, 6         (temp = Number(aRgb[1]).toString(16)).length == 1 ? ('0' + temp) : temp, 7         (temp = Number(aRgb[2]).toString(16)).length == 1 ? ('0' + temp) : temp, 8     ].join(''); 9 }10 11 function hex2rgb(hex) {12     if (hex.length == 3) {13         hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];14     }15     return [16         parseInt(hex[0] + hex[1], 16),17         parseInt(hex[2] + hex[3], 16),18         parseInt(hex[4] + hex[5], 16),19     ].join();20 }

简单来说,就是10进制与16进制的转换。

有个点,就是rgb的三个值,分别对应的是hex的每两个值,比如rgb(255,0,255)对用到hex则分别是 “ff,00,ff”,综合起来就是“#ff00ff”,可以简写“#f0f”。

 

额外效果:

中间的颜色选择还有个效果,就是鼠标拖拽到哪里,就选中相应的颜色。

鼠标拖拽事件大家都不陌生,直接上代码,不废话

 1 can.addEventListener('mousedown', function(e) { 2     var ePos = { 3         x: e.layerX || e.offsetX, 4         y: e.layerY || e.offsetY 5     } 6     if (ePos.x >= 30 && ePos.x < 30 + width && ePos.y >= 0 && ePos.y < width) { 7         document.onmousemove = function(e) { 8             var pos = { 9                 x: e.clientX,10                 y: e.clientY11             }12 13             pos.x = pos.x < 30 ? 30 : pos.x && (pos.x > (30 + width - 1) ? (30 + width - 1) : pos.x);14             pos.y = pos.y < 0 ? 0 : pos.y && (pos.y > (width - 1) ? (width - 1) : pos.y);15 16             rgbaStr = getRgbaAtPoint(pos, 'box');17             cur.style.left = pos.x + 'px';18             cur.style.top = pos.y + 'px';19             cur.style.outlineColor = (rgbaStr[0] > 256 / 2 || rgbaStr[1] > 256 / 2 || rgbaStr[2] > 256 / 2) ? '#000' : '#fff';20             outColor(rgbaStr.slice(0, 3).join());21         };22         document.onmouseup = function() {23             // outColor(rgbaStr.slice(0, 3).join());24             document.onmouseup = document.onmousemove = null;25         }26     }27 28 });

 

 

这样,每段代码拼凑起来,就是整体的架子了,附上最终代码(比较长,折叠了):

  1 <!DOCTYPE html>  2 <html lang="zh">  3   4 <head>  5     <meta charset="UTF-8">  6     <meta name="viewport" content="width=device-width, initial-scale=1.0">  7     <meta http-equiv="X-UA-Compatible" content="ie=edge">  8     <title>Document</title>  9     <style> 10         body { 11             background: #535353; 12             padding: 0; 13             margin: 0; 14         } 15         canvas { 16             cursor: crosshair; 17         } 18         #cur { 19             width: 3px; 20             height: 3px; 21             outline: 2px solid #535353; 22             margin-left: -1px; 23             margin-top: -1px; 24             position: absolute; 25         } 26         .wrapper { 27             position: relative; 28         } 29         #color_show { 30             width: 50px; 31             height: 50px; 32             background: #f00; 33         } 34         .panel { 35             width: 200px; 36             height: 200px; 37             position: fixed; 38             top: 20px; 39             right: 20px; 40             background-color: #fff; 41             padding: 10px; 42             text-align: center; 43             line-height: 2em; 44         } 45     </style> 46 </head> 47  48 <body> 49     <div class="wrapper"> 50         <canvas  width="600" height="600"></canvas> 51         <em ></em> 52         <div class="panel"> 53             <div ></div> 54             <label> 55             rgb <input type="text"  class="color_input" value="" > 56         </label><br> 57             <label> 58             hex <input type="text"  class="color_input" value="" > 59         </label> 60         </div> 61     </div> 62     <script> 63         (function(){ 64             var width = 256; 65             var can = document.getElementById('canvas'); 66             var ctx = can.getContext('2d'); 67             var curColor = 'rgba(255,0,0,1)'; 68             var cur = document.getElementById('cur'); 69             var rgbValue = document.getElementById('rgb_value'); 70             var hexValue = document.getElementById('hex_value'); 71             var colorShow = document.getElementById('color_show'); 72  73             var aColorInput = document.getElementsByClassName('color_input'); 74  75             function colorBar() { 76                 var gradientBar = ctx.createLinearGradient(0, 0, 0, width); 77                 gradientBar.addColorStop(0, '#f00'); 78                 gradientBar.addColorStop(1 / 6, '#f0f'); 79                 gradientBar.addColorStop(2 / 6, '#00f'); 80                 gradientBar.addColorStop(3 / 6, '#0ff'); 81                 gradientBar.addColorStop(4 / 6, '#0f0'); 82                 gradientBar.addColorStop(5 / 6, '#ff0'); 83                 gradientBar.addColorStop(1, '#f00'); 84  85                 ctx.fillStyle = gradientBar; 86                 ctx.fillRect(0, 0, 20, width); 87             } 88  89             function rgb2hex(rgb) { 90                 var aRgb = rgb instanceof Array ? rgb : (rgb.split(',') || [0, 0, 0]); 91                 var temp; 92                 return [ 93                     (temp = Number(aRgb[0]).toString(16)).length == 1 ? ('0' + temp) : temp, 94                     (temp = Number(aRgb[1]).toString(16)).length == 1 ? ('0' + temp) : temp, 95                     (temp = Number(aRgb[2]).toString(16)).length == 1 ? ('0' + temp) : temp, 96                 ].join(''); 97             } 98  99             function hex2rgb(hex) {100                 if (hex.length == 3) {101                     hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];102                 }103                 return [104                     parseInt(hex[0] + hex[1], 16),105                     parseInt(hex[2] + hex[3], 16),106                     parseInt(hex[4] + hex[5], 16),107                 ].join();108             }109 110             function putCurDom(color) {111                 if (/([0-9a-f]{3}|[0-9a-f]{6})/i.test(color)) {112                     // hex113                     color = hex2rgb(color);114                 } else if (color instanceof Array) {115                     color = color.join(',');116                 } else if (/\d{1,3}(\,\d{1,3}){2}/i.test(color)) {117 118                 } else {119                     return;120                 }121             }122 123             function colorBox(color) {124                 // 底色填充,也就是(举例红色)到白色125                 var gradientBase = ctx.createLinearGradient(30, 0, width + 30, 0);126                 gradientBase.addColorStop(1, color);127                 gradientBase.addColorStop(0, 'rgba(255,255,255,1)');128                 ctx.fillStyle = gradientBase;129                 ctx.fillRect(30, 0, width, width);130                 // 第二次填充,黑色到透明131                 var my_gradient1 = ctx.createLinearGradient(0, 0, 0, width);132                 my_gradient1.addColorStop(0, 'rgba(0,0,0,0)');133                 my_gradient1.addColorStop(1, 'rgba(0,0,0,1)');134                 ctx.fillStyle = my_gradient1;135                 ctx.fillRect(30, 0, width, width);136             }137 138             function init() {139                 colorBar();140                 colorBox(curColor);141                 bind();142             }143 144             function bind() {145                 can.addEventListener('click', function(e) {146                     var ePos = {147                         x: e.offsetX || e.layerX,148                         y: e.offsetY || e.layerY149                     }150                     var rgbaStr = '#000';151                     if (ePos.x >= 0 && ePos.x < 20 && ePos.y >= 0 && ePos.y < width) {152                         // in153                         rgbaStr = getRgbaAtPoint(ePos, 'bar');154                         colorBox('rgba(' + rgbaStr + ')');155                     } else if (ePos.x >= 30 && ePos.x < 30 + width && ePos.y >= 0 && ePos.y < width) {156                         rgbaStr = getRgbaAtPoint(ePos, 'box');157                     } else {158                         return;159                     }160                     outColor(rgbaStr.slice(0, 3).join());161                     cur.style.left = ePos.x + 'px';162                     cur.style.top = ePos.y + 'px';163                     cur.style.outlineColor = (rgbaStr[0] > 256 / 2 || rgbaStr[1] > 256 / 2 || rgbaStr[2] > 256 / 2) ? '#000' : '#fff';164                 });165 166                 can.addEventListener('mousedown', function(e) {167                     var ePos = {168                         x: e.layerX || e.offsetX,169                         y: e.layerY || e.offsetY170                     }171                     if (ePos.x >= 30 && ePos.x < 30 + width && ePos.y >= 0 && ePos.y < width) {172                         document.onmousemove = function(e) {173                             var pos = {174                                 x: e.clientX,175                                 y: e.clientY176                             }177 178                             pos.x = pos.x < 30 ? 30 : pos.x && (pos.x > (30 + width - 1) ? (30 + width - 1) : pos.x);179                             pos.y = pos.y < 0 ? 0 : pos.y && (pos.y > (width - 1) ? (width - 1) : pos.y);180 181                             rgbaStr = getRgbaAtPoint(pos, 'box');182                             cur.style.left = pos.x + 'px';183                             cur.style.top = pos.y + 'px';184                             cur.style.outlineColor = (rgbaStr[0] > 256 / 2 || rgbaStr[1] > 256 / 2 || rgbaStr[2] > 256 / 2) ? '#000' : '#fff';185                             outColor(rgbaStr.slice(0, 3).join());186                         };187                         document.onmouseup = function() {188                             // outColor(rgbaStr.slice(0, 3).join());189                             document.onmouseup = document.onmousemove = null;190                         }191                     }192 193                 });194             }195 196             function outColor(rgb) {197                 rgbValue.value = rgb;198                 hexValue.value = rgb2hex(rgb);199                 colorShow.style.backgroundColor = 'rgb(' + rgb + ')';200             }201 202             function getRgbaAtPoint(pos, area) {203                 if (area == 'bar') {204                     var imgData = ctx.getImageData(0, 0, 20, width);205                 } else {206                     var imgData = ctx.getImageData(0, 0, can.width, can.height);207                 }208 209                 var data = imgData.data;210                 var dataIndex = (pos.y * imgData.width + pos.x) * 4;211                 return [212                     data[dataIndex],213                     data[dataIndex + 1],214                     data[dataIndex + 2],215                     (data[dataIndex + 3] / 255).toFixed(2),216                 ];217             }218 219             init();220         })()221     </script>222 </body>223 224 </html>
View Code

 

**********************************************************************

写在最后:

最终写完效果在自己玩耍的过程中,发现浏览器对于canvas的过渡色实现有点问题。chrome很明显,FF稍微好一点。

 

如图: 按道理来说,最下边选到的颜色应该都是rgb(0,0,0)才对,但是图上可见,有些地方并不是~~~

大多数还是000,某些点某个通道有可能会出现1。原因未知。

 

尝试了email给chrome邮箱,可能我英语比较差人家没看懂,也可能我问题没描述清楚,反正后来没有回复,之后的浏览器更新也没有处理。

相应的,css3的过渡色则没有一丁点问题。

**********************************************************************

图片、代码啥的贴了一堆,其中涉及的知识点可能有点多。看到这里的同学,建议回过头再看一遍哈。需要注意的我尽量特殊颜色标出来了。

最后,欢迎留言提建议什么的。

 

1 0
原创粉丝点击