Search code examples
javascriptcsscolorslab-color-space

Color Slider Gradient


I am trying to create a Lab color space slider with a dynamic gradient on the slider. I successfully created the slider (with the help of this library).

I'm having trouble creating the gradients for it. Here's the code relevant to the gradient creation:

setGradient(slider[0], "right", [Laba2cssString(0, Lab.a, Lab.b), Laba2cssString(100, Lab.a, Lab.b)]);
setGradient(slider[1], "right", [Laba2cssString(Lab.L, -128, Lab.b), Laba2cssString(Lab.L, 128, Lab.b)]);
setGradient(slider[2], "right", [Laba2cssString(Lab.L, Lab.a, -128), Laba2cssString(Lab.L, Lab.a, 128)]);

They work as they should, but the colors for A and B (Lab) are wrong. What's the correct way to generate the correct gradient colors?

Here's a website of how the colors should look: colorizer.org

codePen

    var myColor = new Colors(),
      overallSlidersWrapper = document.getElementById('overallSlidersWrapper'),
      slider = document.getElementsByClassName('slider'),
      type,
      mode,
      isLabAB = false,
      currentModeType,
      startPoint,
      currentTarget,
      currentTargetWidth,
      maxReal = {
        Lab: {
          L: 100,
          a: 256,
          b: 256
        }
      };
    var toCSSstring = {
      Lab: Laba2cssString
    };

    var sliderDown = function(e) {
        e.preventDefault();
        if (e.target.classList.contains('sliderRange') || e.target.classList.contains('sliderCursor')) currentTarget = e.target.parentNode;
        else if (e.target.classList.contains('slider')) currentTarget = e.target;
        else if (e.target.classList.contains('leftRoundness')) currentTarget = e.target.nextElementSibling;
        else if (e.target.classList.contains('rightRoundness')) currentTarget = e.target.previousElementSibling;
        else return;


        currentModeType = getModeType(currentTarget);
        type = currentModeType.type;
        mode = currentModeType.mode;
        isLabAB = currentModeType.isLabAB;
        startPoint = getOrigin(currentTarget);

        sliderMove(e);
        addEvent(window, 'mousemove', sliderMove);
        startRender();
      },
      sliderMove = function(e) {
        var newColor = {};
        newColor[mode] = (e.clientX - startPoint.left) /
          currentTarget.offsetWidth * maxReal[type][mode] - (isLabAB ? 128 : 0);
        myColor.setColor(newColor, type);
      };
    renderColorSliders = function(color) {
      for (var n = slider.length; n--;) {
        var currentModeType = getModeType(slider[n]),
          localType = currentModeType.type,
          localMode = currentModeType.mode,
          isLabAB = currentModeType.isLabAB;

        var colorNumber = myColor.colors.RND[localType][localMode],
          percentPosition = (((colorNumber / maxReal[localType][localMode]) + (isLabAB ? 0.5 : 0)) *
            slider[n].offsetWidth) - 7;

        var colorNumber = (localMode === 'alpha') ? myColor.colors.alpha : myColor.colors.RND[localType][localMode],
          percentPosition = (((colorNumber / maxReal[localType][localMode]) + (isLabAB ? 0.5 : 0)) *
            slider[n].offsetWidth) - 7;
        slider[n].firstElementChild.style.transform = 'translateX(' + percentPosition + 'px)';

        slider[n].firstElementChild.style.borderColor = color.RGBLuminance > 0.22 ? 'black' : 'white';
      }
    };

    var result = document.getElementById('result');

    function renderResult(color) {
      result.style.backgroundColor = rgba2cssString(color.RND.rgb.r, color.RND.rgb.g, color.RND.rgb.b);
    }

    function renderGradients(color) {
      Lab = color.RND.Lab;

      setGradient(slider[0], "right", [Laba2cssString(0, Lab.a, Lab.b), Laba2cssString(100, Lab.a, Lab.b)]);
      setGradient(slider[1], "right", [Laba2cssString(Lab.L, -128, Lab.b), Laba2cssString(Lab.L, 128, Lab.b)]);
      setGradient(slider[2], "right", [Laba2cssString(Lab.L, Lab.a, -128), Laba2cssString(Lab.L, Lab.a, 128)]);

      slider[0].previousElementSibling.style.backgroundColor = Laba2cssString(0, Lab.a, Lab.b);
      slider[0].nextElementSibling.style.backgroundColor = Laba2cssString(100, Lab.a, Lab.b);
      slider[1].previousElementSibling.style.backgroundColor = Laba2cssString(Lab.L, -128, Lab.b);
      slider[1].nextElementSibling.style.backgroundColor = Laba2cssString(Lab.L, 128, Lab.b);
      slider[2].previousElementSibling.style.backgroundColor = Laba2cssString(Lab.L, Lab.a, -128);
      slider[2].nextElementSibling.style.backgroundColor = Laba2cssString(Lab.L, Lab.a, 128);
    }

    addEvent(overallSlidersWrapper, 'mousedown', sliderDown);

    function removeMouseUpEvents() {
      removeEvent(window, 'mousemove', sliderMove);
      stopRender();
    }
    addEvent(window, 'mouseup', removeMouseUpEvents);

    var doRender = function(color) {
        renderColorSliders(color);
        renderResult(color);
        renderGradients(color);
      },
      renderTimer,
      startRender = function() {
        renderTimer = setInterval(function() {
          doRender(myColor.colors);
          // http://stackoverflow.com/questions/2940054/
        }, 13); // 1000 / 60); // ~16.666 -> 60Hz or 60fps
      },
      stopRender = function() {
        clearInterval(renderTimer);
      };
    doRender(myColor.colors);

    /*-----------------------------*/
    /*------ Function Helpers -----*/
    /*-----------------------------*/

    function getOrigin(elm) {
      var box = (elm.getBoundingClientRect) ? elm.getBoundingClientRect() : {
          top: 0,
          left: 0
        },
        doc = elm && elm.ownerDocument,
        body = doc.body,
        win = doc.defaultView || doc.parentWindow || window,
        docElem = doc.documentElement || body.parentNode,
        clientTop = docElem.clientTop || body.clientTop || 0, // border on html or body or both
        clientLeft = docElem.clientLeft || body.clientLeft || 0;

      return {
        left: box.left + (win.pageXOffset || docElem.scrollLeft) - clientLeft,
        top: box.top + (win.pageYOffset || docElem.scrollTop) - clientTop
      };
    }

    function addEvent(obj, type, func) {
      addEvent.cache = addEvent.cache || {
        _get: function(obj, type, func, checkOnly) {
          var cache = addEvent.cache[type] || [];

          for (var n = cache.length; n--;) {
            if (obj === cache[n].obj && '' + func === '' + cache[n].func) {
              func = cache[n].func;
              if (!checkOnly) {
                cache[n] = cache[n].obj = cache[n].func = null;
                cache.splice(n, 1);
              }
              return func;
            }
          }
        },
        _set: function(obj, type, func) {
          var cache = addEvent.cache[type] = addEvent.cache[type] || [];

          if (addEvent.cache._get(obj, type, func, true)) {
            return true;
          } else {
            cache.push({
              func: func,
              obj: obj
            });
          }
        }
      };

      if (!func.name && addEvent.cache._set(obj, type, func) || typeof func !== 'function') {
        return;
      }

      if (obj.addEventListener) obj.addEventListener(type, func, false);
      else obj.attachEvent('on' + type, func);
    }

    function removeEvent(obj, type, func) {
      if (typeof func !== 'function') return;
      if (!func.name) {
        func = addEvent.cache._get(obj, type, func) || func;
      }

      if (obj.removeEventListener) obj.removeEventListener(type, func, false);
      else obj.detachEvent('on' + type, func);
    }

    function hasClass(ele, cls) {
      return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
    }

    function getModeType(elem) {
      var id = elem.id, // rgbR
        len = id.length - 1, // 3
        type = id.substr(0, len), // rgb
        mode = id.charAt(len), // r
        isLabAB = type === 'Lab' && (/(?:a|b)/.test(mode)); //is 'Lab && ()'a' || 'b')

      if (elem.id === 'rgbA') mode = 'alpha';

      return {
        type: type,
        mode: mode,
        isLabAB: isLabAB
      };
    }

    /**
     * Formats the given RGB values into a string that can be used in CSS
     */
    function rgba2cssString(r, g, b, a) {
      if (r == null) return;

      if (isObject(r)) r = Object.keys(r).map(function(key) {
        return r[key]
      });
      if (Array.isArray(r)) {
        // Check if array doesn't have alpha
        if (r.length === 3) return rgba2cssString(r[0], r[1], r[2]);
        // Check if array has alpha
        else if (r.length === 4) return rgba2cssString(r[0], r[1], r[2], r[3]);
      }

      if (a || a === 0) return "rgba(" + r + "," + g + "," + b + "," + a + ")";
      return "rgb(" + r + "," + g + "," + b + ")";
    }

    /**
     * Formats the given HSL values into a string that can be used in CSS
     */
    function hsla2cssString(h, s, l, a) {
      if (h == null) return;

      if (isObject(h)) h = Object.keys(h).map(function(key) {
        return h[key]
      });
      if (Array.isArray(h)) {
        // Check if array doesn't have alpha
        if (h.length === 3) return rgba2cssString(h[0], h[1], h[2]);
        // Check if array has alpha
        else if (h.length === 4) return rgba2cssString(h[0], h[1], h[2], h[3]);
      }

      if (a || a === 0) return "hsla(" + h + "," + s + "%," + l + "%," + a + ")";
      return "hsl(" + h + "," + s + "%," + l + "%)";
    }

    /**
     * Formats the given HSV values into a string that can be used in CSS
     */
    function hsva2cssString(h, s, v, a) {
      if (h == null) return;
      var hsvObject,
        alpha;

      if (isObject(h)) {
        hsvObject = h;
        alpha = h.a;
      } else if (Array.isArray(h)) {
        hsvObject = {
          h: h[0],
          s: h[1],
          v: h[2]
        };
        alpha = h[3];
      } else if (s != null) {
        hsvObject = {
          h: h,
          s: s,
          v: v
        };
        alpha = a;
      }

      var rgbColor = myColor.convertColor(hsvObject, 'HSV2RGB');
      rgbColor.a = alpha;
      return rgba2cssString(rgbColor);
    }

    /**
     * Formats the given Lab values into a string that can be used in CSS
     */
    function Laba2cssString(L, a, b, alpha) {
      if (L == null) return;
      var LabObject,
        alphaLocal;

      if (isObject(L)) {
        LabObject = L;
        alphaLocal = L.alpha;
      } else if (Array.isArray(L)) {
        LabObject = {
          L: L[0],
          a: L[1],
          b: L[2]
        };
        alphaLocal = L[3];
      } else if (a != null) {
        LabObject = {
          L: L,
          a: a,
          b: b
        };
        alphaLocal = alpha;
      }

      var rgbColor = myColor.convertColor(LabObject, 'Lab2RGB');
      rgbColor.a = alpha;
      return rgba2cssString(rgbColor);
    }

    function setGradient(el, direction, steps, multipleBG) {
      var gradientString = "linear-gradient(to " + direction + ",";

      stepSize = 100 / (steps.length - 1);

      for (var i = 0; i < steps.length; i++) {
        gradientString += (i > 0 ? "," : "") + steps[i] + (i * stepSize) + "%";
      }
      gradientString += ")";

      if (multipleBG) {
        gradientString += ', ' + multipleBG;
      }
      el.style.backgroundImage = gradientString;
    }

    function isObject(obj) {
      return (typeof obj === "object" && !Array.isArray(obj) && obj !== null);
    }
#overallSlidersWrapper {
  width: 500px;
}
.sliderContent {
  height: 138px;
}
.colorSliderTabsLabel {
  width: calc(100% /4);
  display: inline-block;
  text-align: center;
  cursor: pointer;
}
#colorSliderTabUnderliner {
  height: 3px;
  width: calc(100% /4);
  background-color: green;
  transition: transform 0.3s cubic-bezier(0.45, 0.05, 0.55, 0.95);
}
#tabContentWrapper {
  width: 680px;
  align-items: flex-start;
  position: relative;
}
#overallSlidersWrapper {} .sliderOuterWrapper {
  margin-bottom: 10px;
}
.sliderLabel {} .sliderInnerWrapper {
  height: 18px;
  width: 100%;
  cursor: pointer;
  position: relative;
  display: flex;
}
.slider {
  height: 100%;
  width: calc(100% - 62px);
  /* Subtract TextField (44px) and Both Rounders for Slider (19px each)*/
  position: relative;
  border: 1px solid black;
  border-right: none;
  border-left: none;
}
.leftRoundness,
.rightRoundness {
  width: 9px;
  height: 100%;
  border: 1px solid black;
}
.leftRoundness {
  border-right: none;
  border-top-left-radius: 10px;
  border-bottom-left-radius: 10px;
}
.rightRoundness {
  border-left: none;
  border-top-right-radius: 10px;
  border-bottom-right-radius: 10px;
}
.sliderCursor {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  position: relative;
  border: 2px solid black;
}
#result {
  width: 100px;
  height: 100px;
}
<script src="https://rawgit.com/PitPik/colorPicker/master/colors.js"></script>
<div id="result"></div>

<div id="overallSlidersWrapper">
  <div id="LabSliderContent" class="sliderContent">
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">Lightness</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="LabL" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">a (Green ↔ Red)</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="Laba" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">b (Blue ↔ Yellow)</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="Labb" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
  </div>
</div>


Solution

  • setgradient make a gradient going through the points you define. In your code, you just define the colors in the corners of your slider, so everything in the middle is out of control for you. You can improve the result by adding more points in the way of the gradient as:

    setGradient(slider[0], "right", [Laba2cssString(0, Lab.a, Lab.b), Laba2cssString(50, Lab.a, Lab.b), Laba2cssString(100, Lab.a, Lab.b)]);
    setGradient(slider[1], "right", [Laba2cssString(Lab.L, -128, Lab.b),Laba2cssString(Lab.L, -0, Lab.b), Laba2cssString(Lab.L, 128, Lab.b)]);
    setGradient(slider[2], "right", [Laba2cssString(Lab.L, Lab.a, -128),Laba2cssString(Lab.L, Lab.a, -0), Laba2cssString(Lab.L, Lab.a, 128)]);
    

    The more points you add, the better the sliderbar would look like.