每一個瀏覽器都有本身的特色,好比今天要作的colorpicker就是,一千個瀏覽器,一千個哈姆雷特,一千個colorpicker。今天canvas系列就用canvas作一個colorpicker。css
**********************************************************************html
忽然翻到了以前用js和dom寫的一個colorpicker,比較挫,扔張圖就好(old)vue
這個真的很挫,性能不好,由於每個可選的顏色值都是一個dom,若是要實現256*256,那瀏覽器就爆了~~~~~css3
好,回到今天的demo(new)git
demo連接: https://win7killer.github.io/demo_set/html_demo/canvas/can_ps/color_picker.html
github
沒錯,就是照着PS的顏色選擇器的樣子仿的。chrome
**********************************************************************canvas
首先咱們來看效果圖分析怎麼作:數組
左側提供一系列過渡色,不難看出,這個是「紅黃綠青藍紫」這六種顏色,而後加以過渡色處理來的。最後紫色還要過渡回到紅色。瀏覽器
另外換成環狀的可能更加好識別,以下圖:
那麼,咱們就能夠用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。
中間這塊乍看很簡單,再看有點蒙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繪製的東西就差很少了。
首先明確交互事件:
選擇左側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版本的實現方法。