JavaScript实现HSL拾色器


Posted in Javascript onMay 21, 2020

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] 是饱和度和亮度,计算为:

JavaScript实现HSL拾色器

h 的值通常规范化到位于 0 到 360°之间。而 h = 0 用于 max = min 的(就是灰色)时候而不是留下 h 未定义。
HSL 和 HSV 有同样的色相定义,但是其他分量不同。HSV 颜色的 s 和 v 的值定义如下:

JavaScript实现HSL拾色器

从 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 的时候,可以使用下列过程:

JavaScript实现HSL拾色器

对于每个颜色向量 Color = (ColorR, ColorG, ColorB) = (r, g, b),

JavaScript实现HSL拾色器

从 HSV 到 RGB 的转换

类似的,给定在 HSV 中 (h, s, v) 值定义的一个颜色,带有如上的 h,和分别表示饱和度和明度的 s 和 v 变化于 0 到 1 之间,在 RGB 空间中对应的 (r, g, b) 三原色可以计算为:

JavaScript实现HSL拾色器

对于每个颜色向量 (r, g, b),

JavaScript实现HSL拾色器

<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>

JavaScript实现HSL拾色器

JavaScript实现HSL拾色器

有几个缺陷:

- 不能根据颜色值来设置HSL。
- 由于HSL的值是根据从Hue环形调色板中取出的RGB颜色值换算为HSL的,因此跟滑动条上的值可能会有出入(如果是5舍6入那就一样了)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript使用Replace进行字符串替换的方法
Apr 14 Javascript
jQuery实现大转盘抽奖活动仿QQ音乐代码分享
Aug 21 Javascript
Javascript获取数组中的最大值和最小值的方法汇总
Jan 01 Javascript
js中获取时间new Date()的全面介绍
Jun 20 Javascript
jQuery插件Flexslider实现图片轮播、图文结合滑动切换效果
Apr 16 Javascript
JavaScript性能优化之函数节流(throttle)与函数去抖(debounce)
Aug 11 Javascript
解析JavaScript模仿块级作用域
Dec 29 Javascript
JS字符串长度判断,超出进行自动截取的实例(支持中文)
Mar 06 Javascript
关于JavaScript语句后面的分号问题
Dec 07 Javascript
详解KOA2如何手写中间件(装饰器模式)
Oct 11 Javascript
Layui事件监听的实现(表单和数据表格)
Oct 17 Javascript
vue中的.$mount('#app')手动挂载操作
Sep 02 Javascript
js实现拾色器插件(ColorPicker)
May 21 #Javascript
原生js实现日期选择插件
May 21 #Javascript
vue+Element中table表格实现可编辑(select下拉框)
May 21 #Javascript
浅谈React中组件逻辑复用的那些事儿
May 21 #Javascript
记一次用ts+vuecli4重构项目的实现
May 21 #Javascript
JS实现图片幻灯片效果代码实例
May 21 #Javascript
Javascript实现秒表计时游戏
May 27 #Javascript
You might like
php获取twitter最新消息的方法
2015/04/14 PHP
laravel实现图片上传预览,及编辑时可更换图片,并实时变化的例子
2019/11/14 PHP
讲两件事:1.this指针的用法小探. 2.ie的attachEvent和firefox的addEventListener在事件处理上的区别
2007/04/12 Javascript
JS函数验证总结(方便js客户端输入验证)
2010/10/29 Javascript
基于jquery实现漂亮的动态信息提示效果
2011/08/02 Javascript
JS中sort函数排序用法实例分析
2016/06/16 Javascript
Angular.js中ng-if、ng-show和ng-hide的区别介绍
2017/01/20 Javascript
vue2.0使用Sortable.js实现的拖拽功能示例
2017/02/21 Javascript
原生js实现each方法实例代码详解
2019/05/27 Javascript
[06:43]DAC2018 4.5 SOLO赛 Maybe vs Paparazi
2018/04/06 DOTA
python类型强制转换long to int的代码
2013/02/10 Python
python调用Moxa PCOMM Lite通过串口Ymodem协议实现发送文件
2014/08/15 Python
Python实现的简单hangman游戏实例
2015/06/28 Python
简单理解Python中的装饰器
2015/07/31 Python
Python字符串和字典相关操作的实例详解
2017/09/23 Python
用Python进行简单图像识别(验证码)
2018/01/19 Python
Python实现的简单计算器功能详解
2018/08/25 Python
对Python3+gdal 读取tiff格式数据的实例讲解
2018/12/04 Python
Python函数参数匹配模型通用规则keyword-only参数详解
2019/06/10 Python
pygame实现打字游戏
2021/02/19 Python
Django框架下静态模板的继承操作示例
2019/11/08 Python
Python实现猜年龄游戏代码实例
2020/03/25 Python
Anaconda的安装及其环境变量的配置详解
2020/04/22 Python
python 贪心算法的实现
2020/09/18 Python
Python hashlib模块的使用示例
2020/10/09 Python
Bootstrap File Input文件上传组件
2020/12/01 HTML / CSS
周仰杰(JIMMY CHOO)法国官方网站:闻名世界的鞋子品牌
2019/09/27 全球购物
宣传活动总结范文
2014/07/01 职场文书
中学生运动会通讯稿大全
2014/09/18 职场文书
关于运动会广播稿50字
2014/10/18 职场文书
2015年出纳年终工作总结
2015/05/14 职场文书
mysql批量新增和存储的方法实例
2021/04/07 MySQL
详解JS WebSocket断开原因和心跳机制
2021/05/07 Javascript
vue+spring boot实现校验码功能
2021/05/27 Vue.js
Mysql多层子查询示例代码(收藏夹案例)
2022/03/31 MySQL
JS前端使用canvas实现扩展物体类和事件派发
2022/08/05 Javascript