JavaScript實(shí)現(xiàn)HSL拾色器
HSL 和 HSV 在數(shù)學(xué)上定義為在 RGB 空間中的顏色的 R, G 和 B 的坐標(biāo)的變換。
從 RGB 到 HSL 或 HSV 的轉(zhuǎn)換
設(shè) (r, g, b) 分別是一個(gè)顏色的紅、綠和藍(lán)坐標(biāo),它們的值是在 0 到 1 之間的實(shí)數(shù)。設(shè) max 等價(jià)于 r, g 和 b 中的最大者。設(shè) min 等于這些值中的最小者。要找到在 HSL 空間中的 (h, s, l) 值,這里的 h ∈ [0, 360)是角度的色相角,而 s, l ∈ [0,1] 是飽和度和亮度,計(jì)算為:

h 的值通常規(guī)范化到位于 0 到 360°之間。而 h = 0 用于 max = min 的(就是灰色)時(shí)候而不是留下 h 未定義。
HSL 和 HSV 有同樣的色相定義,但是其他分量不同。HSV 顏色的 s 和 v 的值定義如下:

從 HSL 到 RGB 的轉(zhuǎn)換
給定 HSL 空間中的 (h, s, l) 值定義的一個(gè)顏色,帶有 h 在指示色相角度的值域 [0, 360)中,分別表示飽和度和亮度的s 和 l 在值域 [0, 1] 中,相應(yīng)在 RGB 空間中的 (r, g, b) 三原色,帶有分別對(duì)應(yīng)于紅色、綠色和藍(lán)色的 r, g 和 b 也在值域 [0, 1] 中,它們可計(jì)算為:
首先,如果 s = 0,則結(jié)果的顏色是非彩色的、或灰色的。在這個(gè)特殊情況,r, g 和 b 都等于 l。注意 h 的值在這種情況下是未定義的。
當(dāng) s ≠ 0 的時(shí)候,可以使用下列過程:

對(duì)于每個(gè)顏色向量 Color = (ColorR, ColorG, ColorB) = (r, g, b),

從 HSV 到 RGB 的轉(zhuǎn)換
類似的,給定在 HSV 中 (h, s, v) 值定義的一個(gè)顏色,帶有如上的 h,和分別表示飽和度和明度的 s 和 v 變化于 0 到 1 之間,在 RGB 空間中對(duì)應(yīng)的 (r, g, b) 三原色可以計(jì)算為:

對(duì)于每個(gè)顏色向量 (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");
//十字光標(biāo)顏色
var crossCursorColor = "black";
//十字光標(biāo)線寬
var crossCursorLineWidth = 2;
//十字光標(biāo)某一邊線段長(zhǎng)
var crossCursorHalfLineLen = 5;
//十字光標(biāo)中間斷裂處長(zhǎng)度
var crossCursorHalfBreakLineLen = 2;
//畫布中心點(diǎn)X坐標(biāo)
var centerX = c.width / 2;
//畫布中心點(diǎn)Y坐標(biāo)
var centerY = c.height / 2;
//縮放繪制比例
var scaleRate = 10;
//畫布的內(nèi)切圓半徑(之所以減去一個(gè)數(shù)是為了可以顯示完整的十字光標(biāo))
var innerRadius = Math.min(centerX, centerY) - crossCursorHalfLineLen - crossCursorHalfBreakLineLen;
//內(nèi)切圓半徑的平方
var pow2InnerRadius = Math.pow(innerRadius, 2);
//縮放繪制時(shí)的繪制半徑,即畫布的外徑除以縮放比例
var scaledRadius = Math.sqrt(Math.pow(c.width / 2, 2) + Math.pow(c.height / 2, 2)) / scaleRate;
//由于該圓是由繞圓心的多條線段組成,該值表示將圓分割的份數(shù)
var count = 360;
//一整個(gè)圓的弧度值
var doublePI = Math.PI * 2;
//由于圓心處是多條線段的交匯點(diǎn),Composite是source-over模式,所以后繪制的線段會(huì)覆蓋前一個(gè)線段。另外由于采用線段模擬圓,英雌
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;
//當(dāng)前色相位置X坐標(biāo)
var currentHuePosX = centerX + innerRadius - 1;
//當(dāng)前色相位置Y坐標(biāo)
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();
}
//將整數(shù)轉(zhuǎn)為16進(jìn)制,至少保留2位
function toHexString(intValue) {
var str = intValue.toString(16);
if(str.length == 1) {
str = "0" + str;
}
return str;
}
//判斷坐標(biāo)(x,y)是否在合法的區(qū)域內(nèi)
function isInValidRange(x, y) {
var pow2Distance = Math.pow(x-centerX, 2) + Math.pow(y-centerY, 2);
return pow2Distance >= pow2DeprecatedRadius && pow2Distance <= pow2InnerRadius;
}
//繪制十字光標(biāo)
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);
}
//將對(duì)象中的hsl分量組成一個(gè)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 + "%)";
}
//將對(duì)象中的rgb分量組成一個(gè)rgb顏色(r,g,b在0到255之間)
function formRgbColor(obj) {
return "rgb(" + [obj.r, obj.g, obj.b].join(",") + ")";
}
//從畫布的某點(diǎn)獲取存儲(chǔ)RGB的對(duì)象
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轉(zhuǎn)換為hsl對(duì)象()
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;
}
//創(chuàng)建Hue顏色圓環(huán)
function createHueRing() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0,0,c.width,c.height);
ctx.save();
//將繪制原點(diǎn)移動(dòng)到畫布中心
ctx.translate(centerX, centerY);
//將畫布放大相應(yīng)比例,restore后,繪制內(nèi)容會(huì)縮小
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");
}
//點(diǎn)擊canvas中的Hue拾色圈
function onCanvasClick() {
var x = event.offsetX;
var y = event.offsetY;
if(!isInValidRange(x, y)) {
return;
}
currentHuePosX = x;
currentHuePosY = y;
//創(chuàng)建hue背景圓環(huán)
createHueRing();
setColorValue(x, y);
strokeCrossCursor(x, y);
}
function setColorValue(x, y) {
//獲取包含rgb的顏色對(duì)象
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>


有幾個(gè)缺陷:
- 不能根據(jù)顏色值來設(shè)置HSL。
- 由于HSL的值是根據(jù)從Hue環(huán)形調(diào)色板中取出的RGB顏色值換算為HSL的,因此跟滑動(dòng)條上的值可能會(huì)有出入(如果是5舍6入那就一樣了)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
bootstrap中selectpicker下拉框使用方法實(shí)例
這篇文章主要給大家介紹了關(guān)于bootstrap中selectpicker下拉框使用的相關(guān)資料,文中通過示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
JS switch判斷 三目運(yùn)算 while 及 屬性操作代碼
這篇文章主要介紹了JS switch判斷 三目運(yùn)算 while 及 屬性操作代碼,需要的朋友可以參考下2017-09-09
js為數(shù)字添加逗號(hào)并格式化數(shù)字的代碼
數(shù)字添加逗號(hào)的方法有很多,在本將為大家介紹下使用js來實(shí)現(xiàn),具體如下,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-08-08
How to Auto Include a Javascript File
How to Auto Include a Javascript File...2007-02-02
第一次動(dòng)手實(shí)現(xiàn)bootstrap table分頁效果
這篇文章主要為大家詳細(xì)介紹了第一次動(dòng)手實(shí)現(xiàn)bootstrap table分頁效果的相關(guān)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Javascript驗(yàn)證上傳圖片大小[前臺(tái)處理]
在做上傳圖片的時(shí)候,如果不限制上傳圖片大小,后果非常的嚴(yán)重。解決這個(gè)問題有兩種方式:后臺(tái)處理、前臺(tái)處理2014-07-07

