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