当前位置: 首页 > news >正文

用canvas实现一个colorpicker

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

效果图和demo

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

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

好,回到今天的demo(new)

demo链接: win7killer.github.io/can_ps/src/…

没错,就是照着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 });复制代码

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

按 Ctrl+C 复制代码

按 Ctrl+C 复制代码

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

写在最后:

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

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

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




<!DOCTYPE html>
<html lang="zh">

	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<meta http-equiv="X-UA-Compatible" content="ie=edge">
		<title>Document</title>
		<style>
			body {
				background: #535353;
				padding: 0;
				margin: 0;
			}
			
			canvas {
				cursor: crosshair;
			}
			
			#cur {
				width: 3px;
				height: 3px;
				outline: 2px solid #535353;
				margin-left: -1px;
				margin-top: -1px;
				position: absolute;
			}
			
			.wrapper {
				position: relative;
			}
			
			#color_show {
				width: 50px;
				height: 50px;
				background: #f00;
			}
			
			.panel {
				width: 200px;
				height: 200px;
				position: fixed;
				top: 20px;
				right: 20px;
				background-color: #fff;
				padding: 10px;
				text-align: center;
				line-height: 2em;
			}
		</style>
	</head>

	<body>
		<div class="wrapper">
			<canvas id="canvas" width="600" height="600"></canvas>
			<em id="cur"></em>
			<div class="panel">
				<div id="color_show"></div>
				<label>
            rgb <input type="text"  class="color_input" value="" id="rgb_value">
        </label><br>
				<label>
            hex <input type="text"  class="color_input" value="" id="hex_value">
        </label>
			</div>
		</div>
		<script>
			(function() {
				var width = 256;
				var can = document.getElementById('canvas');
				var ctx = can.getContext('2d');
				var curColor = 'rgba(255,0,0,1)';
				var cur = document.getElementById('cur');
				var rgbValue = document.getElementById('rgb_value');
				var hexValue = document.getElementById('hex_value');
				var colorShow = document.getElementById('color_show');

				var aColorInput = document.getElementsByClassName('color_input');

				function colorBar() {
					var gradientBar = ctx.createLinearGradient(0, 0, 0, width);
					gradientBar.addColorStop(0, '#f00');
					gradientBar.addColorStop(1 / 6, '#f0f');
					gradientBar.addColorStop(2 / 6, '#00f');
					gradientBar.addColorStop(3 / 6, '#0ff');
					gradientBar.addColorStop(4 / 6, '#0f0');
					gradientBar.addColorStop(5 / 6, '#ff0');
					gradientBar.addColorStop(1, '#f00');

					ctx.fillStyle = gradientBar;
					ctx.fillRect(0, 0, 20, width);
				}

				function rgb2hex(rgb) {
					var aRgb = rgb instanceof Array ? rgb : (rgb.split(',') || [0, 0, 0]);
					var temp;
					return [
						(temp = Number(aRgb[0]).toString(16)).length == 1 ? ('0' + temp) : temp,
						(temp = Number(aRgb[1]).toString(16)).length == 1 ? ('0' + temp) : temp,
						(temp = Number(aRgb[2]).toString(16)).length == 1 ? ('0' + temp) : temp,
					].join('');
				}

				function hex2rgb(hex) {
					if(hex.length == 3) {
						hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
					}
					return [
						parseInt(hex[0] + hex[1], 16),
						parseInt(hex[2] + hex[3], 16),
						parseInt(hex[4] + hex[5], 16),
					].join();
				}

				function putCurDom(color) {
					if(/([0-9a-f]{3}|[0-9a-f]{6})/i.test(color)) {
						// hex
						color = hex2rgb(color);
					} else if(color instanceof Array) {
						color = color.join(',');
					} else if(/\d{1,3}(\,\d{1,3}){2}/i.test(color)) {

					} else {
						return;
					}
				}

				function colorBox(color) {
					// 底色填充,也就是(举例红色)到白色
					var gradientBase = ctx.createLinearGradient(30, 0, width + 30, 0);
					gradientBase.addColorStop(1, color);
					gradientBase.addColorStop(0, 'rgba(255,255,255,1)');
					ctx.fillStyle = gradientBase;
					ctx.fillRect(30, 0, width, width);
					// 第二次填充,黑色到透明
					var my_gradient1 = ctx.createLinearGradient(0, 0, 0, width);
					my_gradient1.addColorStop(0, 'rgba(0,0,0,0)');
					my_gradient1.addColorStop(1, 'rgba(0,0,0,1)');
					ctx.fillStyle = my_gradient1;
					ctx.fillRect(30, 0, width, width);
				}

				function init() {
					colorBar();
					colorBox(curColor);
					bind();
				}

				function bind() {
					can.addEventListener('click', function(e) {
						var ePos = {
							x: e.offsetX || e.layerX,
							y: e.offsetY || e.layerY
						}
						var rgbaStr = '#000';
						if(ePos.x >= 0 && ePos.x < 20 && ePos.y >= 0 && ePos.y < width) {
							// in
							rgbaStr = getRgbaAtPoint(ePos, 'bar');
							colorBox('rgba(' + rgbaStr + ')');
						} else if(ePos.x >= 30 && ePos.x < 30 + width && ePos.y >= 0 && ePos.y < width) {
							rgbaStr = getRgbaAtPoint(ePos, 'box');
						} else {
							return;
						}
						outColor(rgbaStr.slice(0, 3).join());
						cur.style.left = ePos.x + 'px';
						cur.style.top = ePos.y + 'px';
						cur.style.outlineColor = (rgbaStr[0] > 256 / 2 || rgbaStr[1] > 256 / 2 || rgbaStr[2] > 256 / 2) ? '#000' : '#fff';
					});

					can.addEventListener('mousedown', function(e) {
						var ePos = {
							x: e.layerX || e.offsetX,
							y: e.layerY || e.offsetY
						}
						if(ePos.x >= 30 && ePos.x < 30 + width && ePos.y >= 0 && ePos.y < width) {
							document.onmousemove = function(e) {
								var pos = {
									x: e.clientX,
									y: e.clientY
								}

								pos.x = pos.x < 30 ? 30 : pos.x && (pos.x > (30 + width - 1) ? (30 + width - 1) : pos.x);
								pos.y = pos.y < 0 ? 0 : pos.y && (pos.y > (width - 1) ? (width - 1) : pos.y);

								rgbaStr = getRgbaAtPoint(pos, 'box');
								cur.style.left = pos.x + 'px';
								cur.style.top = pos.y + 'px';
								cur.style.outlineColor = (rgbaStr[0] > 256 / 2 || rgbaStr[1] > 256 / 2 || rgbaStr[2] > 256 / 2) ? '#000' : '#fff';
								outColor(rgbaStr.slice(0, 3).join());
							};
							document.onmouseup = function() {
								// outColor(rgbaStr.slice(0, 3).join());
								document.onmouseup = document.onmousemove = null;
							}
						}

					});
				}

				function outColor(rgb) {
					rgbValue.value = rgb;
					hexValue.value = rgb2hex(rgb);
					colorShow.style.backgroundColor = 'rgb(' + rgb + ')';
				}

				function getRgbaAtPoint(pos, area) {
					if(area == 'bar') {
						var imgData = ctx.getImageData(0, 0, 20, width);
					} else {
						var imgData = ctx.getImageData(0, 0, can.width, can.height);
					}

					var data = imgData.data;
					var dataIndex = (pos.y * imgData.width + pos.x) * 4;
					return [
						data[dataIndex],
						data[dataIndex + 1],
						data[dataIndex + 2],
						(data[dataIndex + 3] / 255).toFixed(2),
					];
				}

				init();
			})()
		</script>
	</body>

</html>复制代码


相关文章:

  • JDK8新特性(2):Stream API常用操作
  • BZOJ 2457 [BeiJing2011] 双端队列
  • 如何用TensorFlow生成令人惊艳的分形图案
  • Hive SQL 练习(这个秒退是怎么回事啊?写了半天 东西都没了,瞬间整个人都凌乱了)...
  • SylixOS之TFTP使用
  • mysql初探
  • 洛谷——P2862 [USACO06JAN]把牛Corral the Cows
  • web开发经验
  • Zookeeper+ActiveMQ 集群实现
  • Android 使用DDMS查看内存使用情况
  • 新品牌如何开展网络营销?
  • 什么是自动化运维 ? 自动化运维的设计思路以及实战
  • 1.3给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。...
  • html+css+JavaScript例题
  • 通过递归的方式将字符串逆置打印
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 11111111
  • Create React App 使用
  • CSS中外联样式表代表的含义
  • Docker容器管理
  • GraphQL学习过程应该是这样的
  • HTML-表单
  • Java 23种设计模式 之单例模式 7种实现方式
  • js作用域和this的理解
  • leetcode-27. Remove Element
  • Linux CTF 逆向入门
  • nginx 配置多 域名 + 多 https
  • opencv python Meanshift 和 Camshift
  • sessionStorage和localStorage
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • TypeScript迭代器
  • ViewService——一种保证客户端与服务端同步的方法
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • Xmanager 远程桌面 CentOS 7
  • 闭包,sync使用细节
  • 番外篇1:在Windows环境下安装JDK
  • 使用parted解决大于2T的磁盘分区
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 微信小程序:实现悬浮返回和分享按钮
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 正则学习笔记
  • 【干货分享】dos命令大全
  • #etcd#安装时出错
  • #传输# #传输数据判断#
  • (1)STL算法之遍历容器
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (区间dp) (经典例题) 石子合并
  • (三)elasticsearch 源码之启动流程分析
  • (图)IntelliTrace Tools 跟踪云端程序
  • (译) 函数式 JS #1:简介
  • (转载)Linux网络编程入门
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复