每个浏览器都有自己的特点,比如今天要做的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>

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

写在最后:

最终写完效果在自己玩耍的过程中,发现浏览器对于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

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

  1. 【canvas系列】canvas实现“ 简单的Amaziograph效果”--画对称图

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

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

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

  3. 【canvas系列】canvas实现"雷达扫描"效果

    今天来讲解"雷达扫描"效果demo,来源于QQ群里边有群友说想要个雷达效果,就尝试写了一下. 效果图: demo链接: https://win7killer.github.io/c ...

  4. 【canvas系列】用canvas实现一个colorpicker

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

  5. 用canvas实现一个colorpicker

    http://www.cnblogs.com/ufex/p/6382982.html 每个浏览器都有自己的特点,比如今天要做的colorpicker就是,一千个浏览器,一千个哈姆雷特,一千个color ...

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

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

  7. [js高手之路] html5 canvas系列教程 - arcTo(弧度与二次,三次贝塞尔曲线以及在线工具)

    之前,我写了一个arc函数的用法:[js高手之路] html5 canvas系列教程 - arc绘制曲线图形(曲线,弧线,圆形). arcTo: cxt.arcTo( cx, cy, x2, y2, ...

  8. [js高手之路] html5 canvas系列教程 - arc绘制曲线图形(曲线,弧线,圆形)

    绘制曲线,经常会用到路径的知识,如果你对路径有疑问,可以参考我的这篇文章[js高手之路] html5 canvas系列教程 - 开始路径beginPath与关闭路径closePath详解. arc:画 ...

  9. [js高手之路] html5 canvas系列教程 - 图片操作(drawImage,clip,createPattern)

    接着上文[js高手之路] html5 canvas系列教程 - 文本样式(strokeText,fillText,measureText,textAlign,textBaseline)继续,本文介绍的 ...

随机推荐

  1. day 53 Django form 组件 autu 组件

    https://github.com/Endless-Clould/homework/tree/master/auto_zujian ---恢复内容开始--- 前情提要: Django 组件 的 fo ...

  2. postgresql分区表探索(pg_pathman)

    使用场景 许多系统在在使用几年之后数据量不断膨胀,这个时候单表数据量超过2000w+,数据库的查询也越来越慢,而随着时间的推移许多历史数据的重要性可能逐渐下降.这时候就可以考虑使用分区表来将冷热数据分 ...

  3. Xshell连接不上虚拟机Linux系统

    以下是我在尝试网上各种办法之后总结的最优解决办法: 1.先在主机上检查虚拟机相关的必要的服务是否都已经启动 2.检查虚拟机系统防火墙是否处于关闭状态 3.检查虚拟机系统的ssh服务是否已经启动 4.检 ...

  4. Kubernetes使用GlusterFS实现数据持久化

    k8s中部署有状态应用等需要持久化数据的应用,必不可少得用存储,k8s支持很多中存储方案,我司目前使用的存储有glusterfs(分为容器化和裸机方式).nfs供应用选用,本次就简单实战下gluste ...

  5. IO概述、异常、File文件类_DAY19

    IO概述: 操作数据的工具 IO流,即数据流,数据像水流一样通过IO工具进行传输. 程序  <IO>   硬盘 绝对路径与相对路径 1:异常(理解) (1)就是程序的非正常情况. 异常相关 ...

  6. python——利用selenium模仿键盘输入跳转

    这是我以前遇到的一个网站:人卫临床助手,这个网站比较奇怪,不能点击右键查看源码,但是大家可以使用ctrl+U,打开开发者选项,点击network,然后点击第2页和第3页: 可以看到上面的URL是一模一 ...

  7. C/C++ -- Gui编程 -- Qt库的使用 -- Qt窗体的类型状态布局

    -----工程WindowTest----- 1.-----窗体类型type.cpp----- #include <QtGui> int main(int argc, char * arg ...

  8. chroot的用法

    chroot命令用来在指定的根目录下运行指令.chroot,即 change root directory (更改 root 目录).在 linux 系统中,系统默认的目录结构都是以/,即是以根 (r ...

  9. WPF中List的Add()与Insert()方法的区别

    先来看看定义: // Summary: // Adds an object to the end of the System.Collections.Generic.List<T>. // ...

  10. Java并发编程笔记之ThreadLocal源码分析

    多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候, ...