JSFiddle - React, Tailwind, and code Playground

HTML

<div>
    <div class="float-left">
    L<input id="l-in" class="num" type="range" min="0" max="100" value="50.0" step="0.1"/>
    <span id="l-out">50</span><br />
    A<input id="a-in" class="num" type="range" min="-128" max="128" value="0.0" step="0.1"/>
    <span id="a-out">0</span><br />
    B<input id="b-in" class="num" type="range" min="-128" max="128" value="0.0" step="0.1"/>
    <span id="B-out">0</span><br />
    <br />
    XYZ = <span id="x-out"></span>, <span id="y-out"></span>, <span id="z-out"></span>
        <br /><br />
    R<input id="R-in" class="rgb-in" type="range" min="0" max="255" value="119" /><span id="r-out"></span><br />
    G<input id="G-in" class="rgb-in" type="range" min="0" max="255" value="119" /><span id="g-out"></span><br />
    B<input id="B-in" class="rgb-in" type="range" min="0" max="255" value="119" /><span id="b-out"></span><br />
</div>
    <div class="float-left">
        <div id="pv" class="preview"></div>
    </div>
    <div class="clear">
        <canvas id="cnv" width="256" height="256"></canvas>
    </div>
</div>

SCSS

.num, .rgb-in {
    width:20em;
    margin:0 0.25em;
}

.float-left {
    float: left;
}

.clear {
    clear: both;
}

.preview {
    margin: 2em;
    width: 100px;
    height: 100px;
    border: black solid 1px;
}
canvas {
    cursor: crosshair;
}

CoffeeScript

class ColorLAB
  constructor: (@l, @a, @b) ->

  _THRESHOLD = 6 / 29
  _FACTOR = 3 * (Math.pow (6 / 29), 2)
  _CONSTANT = 4 / 29
 
  _f = (t) ->
    if t > _THRESHOLD
      Math.pow t, 3
    else
      _FACTOR * (t - _CONSTANT)

  asXyz: (white=D65) ->
    lf = (@l + 16) / 116
    new ColorXYZ((white.x * _f lf + @a / 500), (white.y * _f lf), (white.z * _f lf - @b / 200))

  asRgb: ->
    @asXyz().asRgb()

class ColorXYZ
  constructor: (@x, @y, @z) ->

  _THRESHOLD = Math.pow (6 / 29), 3
  _FACTOR = (Math.pow (29 / 6), 2) / 3
  _CONSTANT = 4 / 29

  _f = (t) ->
    if t > _THRESHOLD
      Math.pow t, 1/3
    else
      _FACTOR * t + _CONSTANT

  asLab: (white=D65) ->
    fx = _f @x / white.x
    fy = _f @y / white.y
    fz = _f @z / white.z

    new ColorLAB 116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)

  _gamma = (c) ->
    if c < 0.0031308
      12.92 * c
    else
      1.055 * (Math.pow c, 1 / 2.4) - 0.055

  asRgb: ->
    r = ( 3.2406 * @x - 1.5372 * @y - 0.4986 * @z) / 100
    g = (-0.9689 * @x + 1.8758 * @y + 0.0415 * @z) / 100
    b = ( 0.0557 * @x - 0.2040 * @y + 1.0570 * @z) / 100

    r = 255 * _gamma r
    g = 255 * _gamma g
    b = 255 * _gamma b

    new ColorRGB r, g, b

class ColorRGB
  constructor: (@r, @g, @b) ->

  is_in_gamut: ->
    @r >= 0 and @r <= 255 and
    @g >= 0 and @g <= 255 and
    @b >= 0 and @b <= 255
    
  _igamma = (c) ->
    if c < 0.04045
      c / 12.92
    else
      Math.pow ((c + 0.055) / 1.055), 2.4
    
  asXyz: ->
    lr = _igamma @r / 255
    lg = _igamma @g / 255
    lb = _igamma @b / 255
    
    x = 0.4124 * lr + 0.3576 * lg + 0.1805 * lb
    y = 0.2126 * lr + 0.7152 * lg + 0.0722 * lb
    z = 0.0193 * lr + 0.1192 * lg + 0.9502 * lb
    
    new ColorXYZ 100 * x, 100 * y, 100 * z
    
  asLab: ->
    @asXyz().asLab()
    
D65 = new ColorXYZ 95.047, 100.00, 108.883

toHex = (input, length) ->
    output = input.toString(16)
    while output.length < length
        output = '0' + output
    output
    
timeout =...