JavaScript实现HSL拾色器
HSL 和 HSV 在数学上定义为在 RGB 空间中的颜色的 R, G 和 B 的坐标的变换。
从 RGB 到 HSL 或 HSV 的转换
设 (r, g, b) 分别是一个颜色的红、绿和蓝坐标,它们的值是在 0 到 1 之间的实数。设 max 等价于 r, g 和 b 中的最大者。设 min 等于这些值中的最小者。要找到在 HSL 空间中的 (h, s, l) 值,这里的 h ∈ [0, 360)是角度的色相角,而 s, l ∈ [0,1] 是饱和度和亮度,计算为:
h 的值通常规范化到位于 0 到 360°之间。而 h = 0 用于 max = min 的(就是灰色)时候而不是留下 h 未定义。
HSL 和 HSV 有同样的色相定义,但是其他分量不同。HSV 颜色的 s 和 v 的值定义如下:
从 HSL 到 RGB 的转换
给定 HSL 空间中的 (h, s, l) 值定义的一个颜色,带有 h 在指示色相角度的值域 [0, 360)中,分别表示饱和度和亮度的s 和 l 在值域 [0, 1] 中,相应在 RGB 空间中的 (r, g, b) 三原色,带有分别对应于红色、绿色和蓝色的 r, g 和 b 也在值域 [0, 1] 中,它们可计算为:
首先,如果 s = 0,则结果的颜色是非彩色的、或灰色的。在这个特殊情况,r, g 和 b 都等于 l。注意 h 的值在这种情况下是未定义的。
当 s ≠ 0 的时候,可以使用下列过程:
对于每个颜色向量 Color = (ColorR, ColorG, ColorB) = (r, g, b),
从 HSV 到 RGB 的转换
类似的,给定在 HSV 中 (h, s, v) 值定义的一个颜色,带有如上的 h,和分别表示饱和度和明度的 s 和 v 变化于 0 到 1 之间,在 RGB 空间中对应的 (r, g, b) 三原色可以计算为:
对于每个颜色向量 (r, g, b),
<html> <style> .childDiv { display:inline-block; vertical-align:middle; margin-left: 30px; margin-top: 10px; margin-bottom: 10px; } #colorPickDiv { background-color: WhiteSmoke; border: 1px solid LightGrey; padding-top: 20px; padding-bottom: 20px; padding-right: 30px; } #hueTipDiv { margin: 0 0 10px 70px; } #luminanceTipDiv { margin-top: 20px } #colorDiv { width: 100px; height: 100px; background-color: black; } #valueDiv { box-shadow: 0px -5px 10px LightGrey; background-color: WhiteSmoke; border: 1px solid LightGrey; border-top-width: 0; padding-right: 10px; padding-bottom: 10px; } </style> <body> <div> <div id="colorPickDiv"> <div class="childDiv"> <div id="hueTipDiv">Hue:0</div> <canvas id="canvas" width="200" height="200">Your browser does not support canvas</canvas> </div> <div class="childDiv"> <div id="saturationTipDiv" class="divMarginBottom">Saturation:0%</div> <input id="saturationRange" onChange="onHSLRangeChange()" type="range" min="0" max="100" step="1" value="100"/> <div id="luminanceTipDiv" class="divMarginBottom">Luminance:0%</div> <input id="luminanceRange" onChange="onHSLRangeChange()" type="range" min="0" max="100" step="1" value="50"/> </div> <div id="colorDiv" class="childDiv"></div> </div> <div id="valueDiv"> <div class="childDiv"> <div id="hexadecimalTipDiv" class="divMarginBottom">Hexadecimal:</div> <input id="hexadecimalValueDiv" type="text" disabled="disabled"/> </div> <div class="childDiv"> <div id="rgbTipDiv" class="divMarginBottom">RGB:</div> <input id="rgbValueDiv" type="text" readonly="readonly"/> </div> <div class="childDiv"> <div id="hslTipDiv" class="divMarginBottom">HSL:</div> <input id="hslValueDiv" type="text" readonly="readonly"/> </div> </div> </div> <script> var c = document.getElementById("canvas"); var ctx = c.getContext("2d"); var colorDiv = document.getElementById("colorDiv"); var hexadecimalValueDiv = document.getElementById("hexadecimalValueDiv"); var rgbValueDiv = document.getElementById("rgbValueDiv"); var hslValueDiv = document.getElementById("hslValueDiv"); var hexadecimalTipDiv = document.getElementById("hexadecimalTipDiv"); var saturationTipDiv = document.getElementById("saturationTipDiv"); var saturationRange = document.getElementById("saturationRange"); var luminanceTipDiv = document.getElementById("luminanceTipDiv"); var luminanceRange = document.getElementById("luminanceRange"); //十字光标颜色 var crossCursorColor = "black"; //十字光标线宽 var crossCursorLineWidth = 2; //十字光标某一边线段长 var crossCursorHalfLineLen = 5; //十字光标中间断裂处长度 var crossCursorHalfBreakLineLen = 2; //画布中心点X坐标 var centerX = c.width / 2; //画布中心点Y坐标 var centerY = c.height / 2; //缩放绘制比例 var scaleRate = 10; //画布的内切圆半径(之所以减去一个数是为了可以显示完整的十字光标) var innerRadius = Math.min(centerX, centerY) - crossCursorHalfLineLen - crossCursorHalfBreakLineLen; //内切圆半径的平方 var pow2InnerRadius = Math.pow(innerRadius, 2); //缩放绘制时的绘制半径,即画布的外径除以缩放比例 var scaledRadius = Math.sqrt(Math.pow(c.width / 2, 2) + Math.pow(c.height / 2, 2)) / scaleRate; //由于该圆是由绕圆心的多条线段组成,该值表示将圆分割的份数 var count = 360; //一整个圆的弧度值 var doublePI = Math.PI * 2; //由于圆心处是多条线段的交汇点,Composite是source-over模式,所以后绘制的线段会覆盖前一个线段。另外由于采用线段模拟圆,英雌 var deprecatedRadius = innerRadius * 0.3; //废弃圆半径的平方 var pow2DeprecatedRadius = Math.pow(deprecatedRadius, 2); //色相(0-360) var hue; //饱和度(0%-100%) var saturation; //亮度luminance或明度lightness(0%-100%) var luminance; //当前色相位置X坐标 var currentHuePosX = centerX + innerRadius - 1; //当前色相位置Y坐标 var currentHuePosY = centerY; //填充圆 function fillCircle(cx, cy, r, color) { ctx.fillStyle = color; ctx.beginPath(); ctx.arc(cx, cy, r, 0, doublePI); ctx.fill(); } //绘制线条 function strokeLine(x1, y1, x2, y2) { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } //将整数转为16进制,至少保留2位 function toHexString(intValue) { var str = intValue.toString(16); if(str.length == 1) { str = "0" + str; } return str; } //判断坐标(x,y)是否在合法的区域内 function isInValidRange(x, y) { var pow2Distance = Math.pow(x-centerX, 2) + Math.pow(y-centerY, 2); return pow2Distance >= pow2DeprecatedRadius && pow2Distance <= pow2InnerRadius; } //绘制十字光标 function strokeCrossCursor(x, y) { ctx.globalCompositeOperation = "source-over"; ctx.strokeColor = crossCursorColor; ctx.lineWidth = crossCursorLineWidth; strokeLine(x, y-crossCursorHalfBreakLineLen, x, y-crossCursorHalfBreakLineLen-crossCursorHalfLineLen); strokeLine(x, y+crossCursorHalfBreakLineLen, x, y+crossCursorHalfBreakLineLen+crossCursorHalfLineLen); strokeLine(x-crossCursorHalfBreakLineLen, y, x-crossCursorHalfBreakLineLen-crossCursorHalfLineLen, y); strokeLine(x+crossCursorHalfBreakLineLen, y, x+crossCursorHalfBreakLineLen+crossCursorHalfLineLen, y); } //将对象中的hsl分量组成一个hsl颜色(h在0到360之间,s与l均在0到1之间) function formHslColor(obj) { return "hsl(" + obj.h + "," + Math.round(obj.s * 1000)/10 + "%," + Math.round(obj.l * 1000)/10 + "%)"; } //将对象中的rgb分量组成一个rgb颜色(r,g,b在0到255之间) function formRgbColor(obj) { return "rgb(" + [obj.r, obj.g, obj.b].join(",") + ")"; } //从画布的某点获取存储RGB的对象 function getRgbObj(x, y) { var w = 1; var h = 1; var imgData = ctx.getImageData(x,y,w,h); var obj = { r: imgData.data[0], g: imgData.data[1], b: imgData.data[2], a: imgData.data[3] } return obj; } //将rgb转换为hsl对象() function rgbToHslObj(r, g, b) { r /= 255; g /= 255; b /= 255; var max = Math.max(r, g, b); var min = Math.min(r, g, b); var diff = max - min; var twoValue = max + min; var obj = {h:0, s:0, l:0}; if(max == min) { obj.h = 0; } else if(max == r && g >= b) { obj.h = 60 * (g - b) / diff; } else if(max == r && g < b) { obj.h = 60 * (g - b) / diff + 360; } else if(max == g) { obj.h = 60 * (b - r) / diff + 120; } else if(max == b) { obj.h = 60 * (r - g) / diff + 240; } obj.l = twoValue / 2; if(obj.l == 0 || max == min) { obj.s = 0; } else if(0 < obj.l && obj.l <= 0.5) { obj.s = diff / twoValue; //obj.s = diff / (2 * obj.l); } else { obj.s = diff / (2 - twoValue); //obj.s = diff / (2 - 2 * obj.l); } obj.h = Math.round(obj.h); return obj; } //创建Hue颜色圆环 function createHueRing() { ctx.globalCompositeOperation = "source-over"; ctx.clearRect(0,0,c.width,c.height); ctx.save(); //将绘制原点移动到画布中心 ctx.translate(centerX, centerY); //将画布放大相应比例,restore后,绘制内容会缩小 ctx.scale(scaleRate, scaleRate); for(var i=0; i<count; i++) { var degree = i / count * 360; var radian = Math.PI * degree / 180; var x = scaledRadius * Math.cos(radian); var y = scaledRadius * Math.sin(radian); ctx.lineWidth=1; ctx.strokeStyle = "hsl(" + degree +"," + saturation + "," + luminance + ")"; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(0,0); ctx.stroke(); } ctx.restore(); ctx.globalCompositeOperation = "destination-out"; fillCircle(centerX, centerY, deprecatedRadius, "black"); ctx.globalCompositeOperation = "destination-in"; fillCircle(centerX, centerY, innerRadius, "black"); } //点击canvas中的Hue拾色圈 function onCanvasClick() { var x = event.offsetX; var y = event.offsetY; if(!isInValidRange(x, y)) { return; } currentHuePosX = x; currentHuePosY = y; //创建hue背景圆环 createHueRing(); setColorValue(x, y); strokeCrossCursor(x, y); } function setColorValue(x, y) { //获取包含rgb的颜色对象 var rgbObj = getRgbObj(x, y); var rgbColor = formRgbColor(rgbObj); colorDiv.style.backgroundColor = rgbColor; rgbValueDiv.value = rgbColor; var hex = "#" + toHexString(rgbObj.r) + toHexString(rgbObj.g) + toHexString(rgbObj.b); hexadecimalValueDiv.value = hex; var hslObj = rgbToHslObj(rgbObj.r, rgbObj.g, rgbObj.b); hslValueDiv.value = formHslColor(hslObj); hueTipDiv.innerHTML = ("Hue:" + hslObj.h); } function onHSLRangeChange() { //event.target.value; saturation = saturationRange.value + "%"; luminance = luminanceRange.value + "%"; saturationTipDiv.innerHTML = ("Saturation:" + saturation); luminanceTipDiv.innerHTML = ("Luminance:" + luminance); createHueRing(); setColorValue(currentHuePosX, currentHuePosY) strokeCrossCursor(currentHuePosX, currentHuePosY); } function init() { c.addEventListener("click", onCanvasClick); onHSLRangeChange(); } init(); </script> </body> </html>
有几个缺陷:
- 不能根据颜色值来设置HSL。
- 由于HSL的值是根据从Hue环形调色板中取出的RGB颜色值换算为HSL的,因此跟滑动条上的值可能会有出入(如果是5舍6入那就一样了)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。
相关文章
- 本篇文章主要分享了通过window.navigator来判断浏览器及其版本信息的实例代码。具有一定的参考价值,下面跟着小编一起来看下吧...2017-01-23
- 这篇文章主要介绍了js如何实现浏览器打印功能,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-15
- 这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
- 下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
- 作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
- 这篇文章主要介绍了js实现调用网络摄像头及常见错误处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-07
- 这篇文章主要为大家详细介绍了JS实现随机生成验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-06
- 这篇文章主要介绍了js组件SlotMachine实现图片切换效果制作抽奖系统的相关资料,需要的朋友可以参考下...2016-04-19
- 这篇文章主要介绍了基于JavaScript实现文字超出部分隐藏 的相关资料,需要的朋友可以参考下...2016-03-01
- 这篇文章主要为大家详细介绍了js实现列表按字母排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-11
- 这篇文章主要介绍了JS实现响应鼠标点击动画渐变弹出层效果代码,具有非常自然流畅的动画过度效果,涉及JavaScript针对鼠标事件的响应及页面元素样式的动态操作相关技巧,需要的朋友可以参考下...2016-03-28
- 本文给大家介绍的是nodejs实现使用阿里大鱼短信API发送消息的方法和代码,有需要的小伙伴可以参考下。...2016-01-20
- Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统.这篇文章主要介绍了vue.js 表格分页ajax 异步加载数据的相关资料,需要的朋友可以参考下...2016-10-20
- 这次文章要给大家介绍的是node.JS md5加密中文与php结果不一致怎么办,不知道具体解决办法的下面跟小编一起来看看。 因项目需要,需要Node.js与PHP做接口调用,发现nod...2017-07-06
- 这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
- 系统的学习了一下angularjs,发现angularjs的有些思想根php的模块smarty很像,例如数据绑定,filter。如果对smarty比较熟悉的话,学习angularjs会比较容易一点,这篇文章给大家介绍angularjs filter用法详解,感兴趣的朋友一起学习吧...2015-12-29
- 为了网站的安全性,很多朋友都把密码设的比较复杂,但是如何密码不能明显示,不知道输的是对是错,为了安全起见可以把密码显示的,那么基于js代码如何实现的呢?下面通过本文给大家介绍JavaScript实现表单密码的隐藏和显示,需要的朋友参考下...2016-03-03
- 这篇文章主要给大家介绍了一个关于JS正则匹配的踩坑记录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-13
- v-for标签可以用来遍历数组,将数组的每一个值绑定到相应的视图元素中去,下面这篇文章主要给大家介绍了关于在Vue.js中轻松解决v-for执行出错的三个方案,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。...2017-06-15
- 使用require('crypto')调用加密模块。加密模块需要底层系统提供OpenSSL的支持。它提供了一种安全凭证的封装方式,可以用于HTTPS安全网络以及普通HTTP连接。该模块还提供了一套针对OpenSSL的hash(哈希),hmac(密钥哈希),cipher...2014-06-07