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

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

效果图和demo

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

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

好,回到今天的demo(new)

demo链接: https://win7killer.github.io/demo_set/html_demo/canvas/can_ps/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.clientY
11             }
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 id="canvas" width="600" height="600"></canvas>
 51         <em id="cur"></em>
 52         <div class="panel">
 53             <div id="color_show"></div>
 54             <label>
 55             rgb <input type="text"  class="color_input" value="" id="rgb_value">
 56         </label><br>
 57             <label>
 58             hex <input type="text"  class="color_input" value="" id="hex_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                     // hex
113                     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.layerY
149                     }
150                     var rgbaStr = '#000';
151                     if (ePos.x >= 0 && ePos.x < 20 && ePos.y >= 0 && ePos.y < width) {
152                         // in
153                         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.offsetY
170                     }
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.clientY
176                             }
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的过渡色则没有一丁点问题。

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

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

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

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

另附新款color-picker, 基于css和js计算去实现,规避上班canvas过渡色问题。

不过用了vue组件去实现,有些小问题懒得去处理【是有多懒,多年不写文章就知道(也可能是忙呢)】,回头在不一篇随笔,介绍js版本的实现方法。

https://win7killer.github.io/#/vue_demo/ColorPicker

更多专业前端知识,请上 【猿2048】www.mk2048.com

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

  1. 从0开始canvas系列一 --- canvas画布

    从0开始canvas系列 从0开始canvas系列一 - canvas画布 从0开始canvas系列二 - 文本和图像 从0开始canvas系列三 - 图像像素级操作 从0开始canvas系列四 - ...

  2. 【canvas系列】canvas实现“ 简单的Amaziograph效果”--画对称图【强迫症福利】

    标题很难引人入胜,先放个效果图好了 如果图片吸引不了你,那我觉得也就没啥看的了. demo链接: https://win7killer.github.io/demo_set/html_demo/can ...

  3. java 选择 颜色的控件_JavaFX颜色选择器(ColorPicker)

    颜色选择器控件允许用户从可用的颜色范围中选择颜色,或通过指定RGB或HSB组合设置其他颜色.JavaFX ColorPicker控件具有颜色选择器,调色板和自定义颜色对话框窗口. 创建ColorPic ...

  4. 练习-原生js写的颜色选择器colorpicker

    colorpicker 文章目录 colorpicker 需求分析: 第一种方案的颜色选择器: 点击色块选择颜色 进行了改进, 把整个颜色选择器封装到js文件里, 通过appendChild的形式添加 ...

  5. canvas入门系列之如何画一个饼图

    html <canvas id="c1" width="1000px" height="600px"><span>不 ...

  6. 前端画圆弧html弧线的像素,[js高手之路] html5 canvas系列教程 - arc绘制曲线图形(曲线,弧线,圆形)...

    arc:画弧度 cxt.arc( x, y, 半径, 开始角度,结束角度,是否逆时针 ); x, y: 为弧度的中心横坐标和纵坐标,如果这是画一个圆.那么x,y就是圆的圆心. 开始角度与结束角度都是以 ...

  7. [js高手之路] html5 canvas系列教程 - 线条样式(lineWidth,lineCap,lineJoin,setLineDash)

    上文,写完弧度与贝塞尔曲线[js高手之路] html5 canvas系列教程 - arcTo(弧度与二次,三次贝塞尔曲线以及在线工具),本文主要是关于线条的样式设置 lineWidth: 设置线条的宽 ...

  8. [js高手之路] html5 canvas系列教程 - 掌握画直线图形的常用API

    我们接着上文[js高手之路] html5 canvas系列教程 - 认识canvas以及基本使用方法继续. 一.直线的绘制 cxt.moveTo( x1, y1 ): 将画笔移动到x1, y1这个点 ...

  9. [Canvas系列]Canvas简单线条绘制_02

    在学画画的时候,线条是最基本的了,而线条的连接可以组成任何图形.在Canvas中也是如此. 在开始之前我们先拿出画布和画笔: 1 2 var cvs = document. getElementByI ...

最新文章

  1. OkHttp源码分析
  2. 敏捷转型历程 - Sprint3 回顾会
  3. 【Apache】指定 某一个URL 并进行 Auth 认证
  4. ai背景合成_AI设计制作万圣节夜景插画
  5. oracle一体机flash卡,PCIe Flash卡设备的测试
  6. 一个java程序_从另一个java程序运行java程序
  7. Qt信号阻塞和断开信号槽
  8. 哲学家晚餐问题的Haskell求解
  9. asp中把数据导出为excel的2种方法
  10. java list移除所有元素_Java - List集合中如何删除多个元素? remove( )方法 ?
  11. vue生成证书模板 并支持图片和PDF格式下载的小demo
  12. cad角度怎么画_软件CAD | 各种“线”工具
  13. hyperv创建ubuntu20.10 ubuntu18.04虚拟机
  14. JS下载不带后缀名的文件,下载后自动加了后缀名,如何处理?
  15. 计算机未连接到网络,电脑未连接到一个互联网的问题,解决网络问题方法
  16. python编码的种类以及转换以及bytes数据类型的介绍
  17. 探索TiDB Lightning源码来解决发现的bug
  18. org.eclipse.core.runtime.IPath报错
  19. 如何区分研究背景与研究意义
  20. 外贸群发软件不好用,邮件群发很苦恼

热门文章

  1. 车万翔:ChatGPT时代,NLPer 的危与机
  2. 华为高通携LTE技术杀入车联网,正面挑战V2X老标准
  3. 利用opencv识别路标
  4. Ubuntu安装Mysql启用远程连接
  5. 关于可能的OPPO HR面
  6. HTC U11 EYEs刷机包 HTC U11 EYEs原厂系统维修线刷包msm8976含教程
  7. 如何获取支付宝小程序的appid
  8. 申请计算机专业有关个人陈述吗,美国计算机博士申请个人陈述范文
  9. 图像化界面开发之QT入门
  10. 中国知网文献引用导入EndNote9.X,Web of science导入endnote以及谷歌学术导入endnote图文详解,全网最细版本适用EndNote9.x,Endnote20版本