hsv color picker
by sfoster
HTML
<p>
<span class="sample" style="background-color: hsl(0, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(45, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(90, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(135, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(180, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(225, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(270, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(315, 100%, 50%);"></span>
<span class="sample" style="background-color: hsl(360, 100%, 50%);"></span>
</p>
<p>
<label for="in-startColor">startColor:</label>
<input name="startColor" id="in-startColor" data-hsv="hsv(360, 0, 153)" readonly value="#000000" class="swatch">
</p>
<p>
<label for="in-endColor">endColor:</label>
<input name="endtColor" id="in-endColor" readonly value="#000000" class="swatch">
</p>
<p>
<label for="in-duration">duration:</label>
<input name="duration" id="in-duration" type="text" value="1000">
</p>
<div id="colorPickerOverlay">
<div id="dot"></div>
<img id="colorWheel"...
CSS
#dot {
position: absolute;
left: 0;
top: 0;
width: 6px;
height: 6px;
border-radius: 3px;
background-color: rgba(0,0,0,0.5);
transform: translate(-3px, -3px);
z-index: 10;
}
#colorPickerOverlay {
position: absolute;
top: 0; bottom: 0; left: 0; right: 0;
background-color: rgba(153, 153, 153, 0.5);
z-index: 10;
visibility: hidden;
}
#colorWheel {
position: absolute;
left: calc(50vw - 105px);
left: calc(50vh - 105px);
width: 210px;
height: 210px;
background-color: rgba(0,0,0,0.5);
}
.swatch {
outline: 1px solid #ccc;
display: inline-block;
width: 5em;
height: 2em;
padding: 4px 6px;
border: none;
cursor: pointer;
}
input.swatch {
color: transparent;
background-color: attr(data-hsv);
}
img {
outline: 1px solid #ccc;
}
.sample {
display: inline-block;
width: 8em; height: 2em;
outline: 1px dotted #999;
}
JavaScript
function HSVtoHSL(h, s, v) {
let _h = h,
_s = s * v,
_l = (2 - s) * v;
_s /= (_l <= 1) ? (_l === 0 ? 1 : _l) : 2 - _l;
_l /= 2;
return {
h: _h,
s: _s,
l: _l,
};
}
const $ = document.querySelector.bind(document);
const app = {
hsvWheelImage: $("#colorWheel"),
colorPickerOverlay: $("#colorPickerOverlay"),
dot: $("#dot"),
init: function () {
this._activeSwatch = $("#in-startColor");
this.setActiveColor(90, 1, 1);
for (let swatch of document.querySelectorAll("input.swatch")) {
swatch.addEventListener('click', this);
}
},
handleEvent: function(evt) {
if (evt.type == "click" && evt.target == this.hsvWheelImage) {
this.handleColorWheelClick(evt);
}
if (evt.type == "click" && evt.target.classList.contains("swatch")) {
this.handleColorInputClick(evt);
}
},
handleColorWheelClick: function (evt) {
let rect = evt.target.getBoundingClientRect();
this.dot.style.left = evt.x +"px";
this.dot.style.top = evt.y + "px";
let x = evt.x - rect.x - rect.width/2;
let y = evt.y - rect.y;
y = rect.height - y - rect.height/2;
let rad, deg;
let adj = x;
let opp = y;
if (x < 0) {
rad = Math.atan(opp/adj);
deg = 270 - rad / (Math.PI/180);
} else {
rad = Math.atan(opp/adj);
deg = (360 - rad / (Math.PI/180)) % 270;
}
this.setActiveColor(deg, 1, 1);
evt.target.removeEventListener("click", this);
this.colorPickerOverlay.style.visibility = "hidden";
},
handleColorInputClick: function(evt) {
this.colorPickerOverlay.style.visibility = "visible";
this._activeSwatch = evt.target;
this.hsvWheelImage.addEventListener('click', this);
},
setActiveColor(hueDeg, sat, val) {
this._activeSwatch.value = [
hueDeg/360*255|0,
sat*255|0,
val*255|0
...