4
\$\begingroup\$

I am creating a color slider, and I'm trying to make the slider's background-color a dynamic gradient. I've successfully did it, but I feel this can be done in a lot less code than what I have.

What I currently have is, I go through every slider (without a loop), and give it the correct gradient. I'm sure there is a couple liner thing that can do it, and I want to know what that is.

On each slider there's a div on the right and left side. Each one of those are half a circle, and solid colors. The reason I don't make the roundness part of the actual slider, (i.e. border-bottom-right-radius: 10px;) is because the color won't be 100% accurate then. I think. Please correct me if I'm wrong.

Here's the code for just one slider. I have a lot more though:

setGradient(slider[0], "right", [rgba2cssString(0, rgb.g, rgb.b), rgba2cssString(255, rgb.g, rgb.b)]);
slider[0].previousElementSibling.style.backgroundColor = rgba2cssString(0, rgb.g, rgb.b);
slider[0].nextElementSibling.style.backgroundColor = rgba2cssString(255, rgb.g, rgb.b);

Please let me know what you think about this whole thing, and if you have any suggestions, other than to use jQuery.

codePen

    var myColor = new Colors(),
      overallSlidersWrapper = document.getElementById('overallSlidersWrapper'),
      slider = document.getElementsByClassName('slider'),
      type,
      mode,
      isLabAB = false,
      startPoint,
      currentTargetWidth,
      maxReal = {
        rgb: {
          r: 255,
          g: 255,
          b: 255
        },
        hsl: {
          h: 360,
          s: 100,
          l: 100
        },
        hsv: {
          h: 360,
          s: 100,
          v: 100
        },
        Lab: {
          L: 100,
          a: 256,
          b: 256
        }
      };

    var sliderDown = function(e) {
        var target = e.target || e.srcElement,
          currentTarget;

        if (target !== this) {

          if (e.preventDefault) e.preventDefault();

          if (hasClass(target, 'sliderRange') || hasClass(target, 'sliderCursor')) {
            currentTarget = target.parentNode;
          } else if (hasClass(target, 'slider')) {
            currentTarget = target;
          } else if (hasClass(target, 'leftRoundness')) {
            currentTarget = target.nextElementSibling;
          } else if (hasClass(target, 'rightRoundness')) {
            currentTarget = target.previousElementSibling;
          } else {
            return;
          }

          var currentModeType = getModeType(currentTarget);
          type = currentModeType.type;
          mode = currentModeType.mode;
          isLabAB = currentModeType.isLabAB;

          startPoint = getOrigin(currentTarget);
          currentTargetWidth = currentTarget.offsetWidth;

          sliderMove(e);
          addEvent(window, 'mousemove', sliderMove);
          startRender();
        }
      },
      sliderMove = function(e) {
        var newColor = {};
        newColor[mode] = (e.clientX - startPoint.left) / currentTargetWidth * maxReal[type][mode];
        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;

          slider[n].firstElementChild.style.left = (color.RND[localType][localMode] / maxReal[localType][localMode] * 100) + '%';

          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) {
      var rgb = color.RND.rgb,
        hsl = color.RND.hsl,
        hsv = color.RND.hsv,
        Lab = color.RND.Lab;

      setGradient(slider[0], "right", [rgba2cssString(0, rgb.g, rgb.b), rgba2cssString(255, rgb.g, rgb.b)]);
      setGradient(slider[1], "right", [rgba2cssString(rgb.r, 0, rgb.b), rgba2cssString(rgb.r, 255, rgb.b)]);
      setGradient(slider[2], "right", [rgba2cssString(rgb.r, rgb.g, 0), rgba2cssString(rgb.r, rgb.g, 255)]);

      setGradient(slider[3], "right", [hsla2cssString(0, hsl.s, hsl.l), hsla2cssString(60, hsl.s, hsl.l), hsla2cssString(120, hsl.s, hsl.l), hsla2cssString(180, hsl.s, hsl.l), hsla2cssString(300, hsl.s, hsl.l), hsla2cssString(360, hsl.s, hsl.l)]);
      setGradient(slider[4], "right", [hsla2cssString(hsl.h, 0, hsl.l), hsla2cssString(hsl.h, 100, hsl.l)]);
      setGradient(slider[5], "right", [hsla2cssString(hsl.h, hsl.s, 0), hsla2cssString(hsl.h, hsl.s, 50), hsla2cssString(hsl.h, hsl.s, 100)]);

      setGradient(slider[6], "right", [hsva2cssString(0, hsv.s, hsv.v), hsva2cssString(60, hsv.s, hsv.v), hsva2cssString(120, hsv.s, hsv.v), hsva2cssString(180, hsv.s, hsv.v), hsva2cssString(300, hsv.s, hsv.v), hsva2cssString(360, hsv.s, hsv.v)]);
      setGradient(slider[7], "right", [hsva2cssString(hsv.h, 0, hsv.v), hsva2cssString(hsv.h, 100, hsv.v)]);
      setGradient(slider[8], "right", [hsva2cssString(hsv.h, hsv.s, 0), hsva2cssString(hsv.h, hsv.s, 50), hsva2cssString(hsv.h, hsv.s, 100)]);

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

      slider[0].previousElementSibling.style.backgroundColor = rgba2cssString(0, rgb.g, rgb.b);
      slider[0].nextElementSibling.style.backgroundColor = rgba2cssString(255, rgb.g, rgb.b);
      slider[1].previousElementSibling.style.backgroundColor = rgba2cssString(rgb.r, 0, rgb.b);
      slider[1].nextElementSibling.style.backgroundColor = rgba2cssString(rgb.r, 255, rgb.b);
      slider[2].previousElementSibling.style.backgroundColor = rgba2cssString(rgb.r, rgb.g, 0);
      slider[2].nextElementSibling.style.backgroundColor = rgba2cssString(rgb.r, rgb.g, 255);

      slider[3].previousElementSibling.style.backgroundColor = hsla2cssString(0, hsl.s, hsl.l);
      slider[3].nextElementSibling.style.backgroundColor = hsla2cssString(360, hsl.s, hsl.l);
      slider[4].previousElementSibling.style.backgroundColor = hsla2cssString(hsl.h, 0, hsl.l);
      slider[4].nextElementSibling.style.backgroundColor = hsla2cssString(hsl.h, 100, hsl.l);
      slider[5].previousElementSibling.style.backgroundColor = hsla2cssString(hsl.h, hsl.s, 0);
      slider[5].nextElementSibling.style.backgroundColor = hsla2cssString(hsl.h, hsl.s, 100);

      slider[6].previousElementSibling.style.backgroundColor = hsva2cssString(0, hsv.s, hsv.v);
      slider[6].nextElementSibling.style.backgroundColor = hsva2cssString(360, hsv.s, hsv.v);
      slider[7].previousElementSibling.style.backgroundColor = hsva2cssString(hsv.h, 0, hsv.v);
      slider[7].nextElementSibling.style.backgroundColor = hsva2cssString(hsv.h, 100, hsv.v);
      slider[8].previousElementSibling.style.backgroundColor = hsva2cssString(hsv.h, hsv.s, 0);
      slider[8].nextElementSibling.style.backgroundColor = hsva2cssString(hsv.h, hsv.s, 100);

      slider[9].previousElementSibling.style.backgroundColor = Laba2cssString(0, Lab.a, Lab.b);
      slider[9].nextElementSibling.style.backgroundColor = Laba2cssString(100, Lab.a, Lab.b);
      slider[10].previousElementSibling.style.backgroundColor = Laba2cssString(Lab.L, -128, Lab.b);
      slider[10].nextElementSibling.style.backgroundColor = Laba2cssString(Lab.L, 128, Lab.b);
      slider[11].previousElementSibling.style.backgroundColor = Laba2cssString(Lab.L, Lab.a, -128);
      slider[11].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
      };
    }

    function rgba2cssString(r, g, b, a) {
      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 (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) {
      var hslColor = myColor.convertColor({
        h: h,
        s: s,
        v: v
      }, 'HSV2HSL');

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

    /**
     * Formats the given Lab values into a string that can be used in CSS
     */
    function Laba2cssString(L, a, b, alpha) {
      var rgbColor = myColor.convertColor({
        L: L,
        a: a,
        b: b
      }, 'LAB2RGB');

      if (alpha || alpha === 0) {
        return "rgba(" + rgbColor.r + "," + rgbColor.g + "," + rgbColor.b + "," + alpha + ")";
      }
      return "rgb(" + rgbColor.r + "," + rgbColor.g + "," + rgbColor.b + ")";
    }

    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;
    }
#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://cdn.rawgit.com/PitPik/colorPicker/master/colors.js"></script>
<div id="result"></div>

<div id="overallSlidersWrapper">
  <div id="rgbSliderContent" class="sliderContent">
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">red</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="rgbr" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">green</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="rgbg" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">blue</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="rgbb" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
  </div>
  <div id="hslSliderContent" class="sliderContent">
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">hue</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="hslh" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">saturation</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="hsls" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">lightness</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="hsll" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
  </div>
  <div id="hsvSliderContent" class="sliderContent">
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">hue</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="hsvh" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">saturation</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="hsvs" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
    <div class="sliderOuterWrapper">
      <div class="sliderLabel">value</div>
      <div class="sliderInnerWrapper">
        <div class="leftRoundness"></div>
        <div id="hsvv" class="slider">
          <div class="sliderCursor"></div>
        </div>
        <div class="rightRoundness"></div>
      </div>
    </div>
  </div>
  <div id="LabSliderContent" class="sliderContent" style="width: 0px; overflow: hidden;">
    <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>

\$\endgroup\$
  • \$\begingroup\$ i have no answer - but I wanted to say that really like your color sliders!!! nice work! \$\endgroup\$ – gavgrif Apr 7 '16 at 1:09
  • 3
    \$\begingroup\$ "go through every slider (without a loop)" is exactly your problem. And already gives the solution: Use a loop. \$\endgroup\$ – Bergi Apr 7 '16 at 1:10
  • 2
    \$\begingroup\$ I'm/I am - please refrain from repetitively making such trivial edits. Your post looks good now. I hope you get good reviews! \$\endgroup\$ – Mathieu Guindon Apr 8 '16 at 17:26
1
\$\begingroup\$

From another answer of mine:

You don't use quotes or apostrophes for string literals consistently. Decide if you want to write string using either apostrophes or double quotes and use it consistently in the whole code. Personally, I prefer apostrophes, because they require to press only one key, whereas to insert a double quote character you have to hold Shift too.

And for example if you choose apostrophes and you want to make a string which contains apostrophes, feel free to use double quotes in this specific situation, so you don't have to escape the apostrophes with a backslash.


You're not using the const keyword anywhere. It's a good practice to use const instead of var always when possible, that is, when you don't intend to reassign the variable. It can prevent you from accidentally reassigning some other value to the variable, because it throws an error if you try to (at least in strict mode).


You should wrap your code in an IIFE. This prevents you from polluting the global scope. See What does it mean global namespace would be polluted?.


var target = e.target || e.srcElement

Avoid the x = x || y construct. It's often interpreted as "if x is not undefined, assign x to x, otherwise assign y to x", while it actually means "if x is not falsey, assign x to x, otherwise assign y to x". Therefore, if x is an empty string, false, 0, or any other falsey value, y will be assigned to it. And even if in some cases it works, because you know that x will never be falsey except of undefined, it's still discouraged because it's hard to understand for JavaScript newbies.

Instead, use a ternary operator and check if a variable is undefined using typeof operator:

x = typeof x !== "undefined" ? x : y

See What does the construct x = x || y mean? for more information.


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

Now almost all browsers support Element.classList. You can simply use .contains() method of it to check whether an element has specified class:

function hasClass(element, className) {
  return element.classList.contains(className)
}

var sliderDown = function(e) {}

I would call that parameter event instead of meaningless e.


if (e.preventDefault) e.preventDefault();

I'm not sure what's the point of the if statement here. If it's to detect if a browser support event.preventDefault method — all browsers support it now. And even if for some reason you decide to check it anyway, you should provide an alternative method, like return false;.


for (var n = slider.length; n--;)

You could use let instead of var here.


You have some very lengthy lines, like this:

setGradient(slider[3], "right", [hsla2cssString(0, hsl.s, hsl.l), hsla2cssString(60, hsl.s, hsl.l), hsla2cssString(120, hsl.s, hsl.l), hsla2cssString(180, hsl.s, hsl.l), hsla2cssString(300, hsl.s, hsl.l), hsla2cssString(360, hsl.s, hsl.l)])

It's not very readable. I would break it like that:

setGradient(slider[3], "right", [
  hsla2cssString(0, hsl.s, hsl.l),
  hsla2cssString(60, hsl.s, hsl.l),
  hsla2cssString(120, hsl.s, hsl.l),
  hsla2cssString(180, hsl.s, hsl.l),
  hsla2cssString(300, hsl.s, hsl.l),
  hsla2cssString(360, hsl.s, hsl.l)
])
\$\endgroup\$
  • \$\begingroup\$ Also, opera mini doesn't support classList. see: caniuse.com/#search=classlist \$\endgroup\$ – Horay Apr 10 '16 at 22:29
  • \$\begingroup\$ @Horay Well, even Stack Exchange doesn't support Opera Mini, I don't see any reason why you should support it. \$\endgroup\$ – Michał Perłakowski Apr 11 '16 at 8:17
  • \$\begingroup\$ Ok. I was just making sure. Thanks! Did you get my first comment? \$\endgroup\$ – Horay Apr 11 '16 at 18:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.