Edit in JSFiddle

const utils = {
  // 事件绑定
  addHandler(target, event, handler) {
    if (target.addEventListener) {
      target.addEventListener(event, handler, false);
    } else if (target.attachEvent) {
      target.attachEvent('on' + event, handler);
    } else {
      target['on' + event] = handler;
    }
  },
  // 事件解绑
  removeHandler(target, event, handler) {
    if (target.removeEventListener) {
      target.removeEventListener(event, handler, false);
    } else if (target.detachEvent) {
      target.detachEvent('on' + event, handler);
    } else {
      target['on' + event] = null;
    }
  },
  // 坐标锁定
  clamp(val, min, max) {
    return val < min ? min : (val > max ? max : val);
  },
  // 一个不考虑兼容性的domready
  domready(callback) {
    document.addEventListener('DOMContentLoaded', listener = function () {
      document.removeEventListener('DOMContentLoaded', listener);
      callback();
    });
  },
  // 判断是否为数组
  isArray(arrayLike) {
    return Object.prototype.toString.call(arrayLike) === '[object Array]';
  },
  // 数字末尾除0
  trimZero(str) {
    return str.replace(/\.?0*$/, '');
  },
  color: {
    // https://gist.github.com/NV/522734
    // hsb颜色转为hsl颜色
    hsb2hsl(h, s, b) {
      var hsl = {
        h: h
      };
      hsl.l = (2 - s) * b;
      hsl.s = s * b;
      if (hsl.l <= 1 && hsl.l > 0) {
        hsl.s /= hsl.l;
      } else {
        hsl.s = hsl.s / (2 - hsl.l) || 0;
      }
      hsl.l /= 2;
      if (hsl.s > 1) {
        hsl.s = 1;
      }
      return hsl;
    },
    /**
     * Converts an HSL color value to RGB. Conversion formula
     * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
     * Assumes s and l are contained in the set [0, 1] and h is 
     * contained in the set [0, 360], returns r, g, and b in the
     * set [0, 255].
     *
     * @param   {number}  h       The hue
     * @param   {number}  s       The saturation
     * @param   {number}  l       The lightness
     * @return  {Array}           The RGB representation
     */
    // hsl颜色转为rgb颜色
    hsl2rgb(h, s, l) {
      h = h / 360;
      var r, g, b;
      if (s == 0) {
        r = g = b = l; // achromatic
      } else {
        var hue2rgb = function hue2rgb(p, q, t) {
          if (t < 0) t += 1;
          if (t > 1) t -= 1;
          if (t < 1 / 6) return p + (q - p) * 6 * t;
          if (t < 1 / 2) return q;
          if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
          return p;
        }
        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hue2rgb(p, q, h + 1 / 3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1 / 3);
      }
      return {r, g, b};
    },
    // rgb颜色转为hex颜色
    rgb2hex(r, g, b) {
      return "#" + (16777216 | (b * 255) | ((g * 255) << 8) | ((r * 255) << 16)).toString(16).slice(1);
    }
  }
}

class DragContext {
  constructor({
    $context, // 可以拖动的范围,即色盘以及滑块槽
    $dragger, // 可拖动对象,色盘游标以及滑块
    name, // 用来区分两个滑块
    direction // 用来区分仅x轴还是x,y轴均可拖动
  }) {
   	this.$context = $context;
    this.$dragger = $dragger;
    this.name = name;
    this.direction = direction;
    this._isDragging = false;
    this._x = 0;
    this._y = 0;
    this._rect = this.$context.getBoundingClientRect();
  }
  init() {
    this._addMousedown();
    this._addMousemove();
    this._addMouseup();
  }
  _addMousedown() {
    utils.addHandler(this.$context, 'mousedown', (e) => {
      // 初始化样式
      this._setStyles(e);
      this._isDragging = true;
    });
  }
  _addMousemove() {
    utils.addHandler(document, 'mousemove', (e) => {
      if (this._isDragging) {
        this._setStyles(e);
      }
    });
  }
  _addMouseup() {
    utils.addHandler(document, 'mouseup', (e) => {
      this._isDragging = false;
    });
  }
  
  _setStyles(e) {
    this._setDraggerStyles(e);
    this._setContextStyles(e);
  }
  _setDraggerStyles(e) {
    // 设置dragger样式
    this._x = utils.clamp(e.clientX - this._rect.left, 0, this._rect.width);
    this._y = utils.clamp(e.clientY - this._rect.top, 0, this._rect.height);
    switch (this.direction) {
      case 'horizontal':
        this.$dragger.style.transform = `translate(${this._x}px, 0)`;
        break;
      case 'vertical':
        this.$dragger.style.transform = `translate(0, ${this._y}px)`;
        break;
      case 'both':
        this.$dragger.style.transform = `translate(${this._x}px, ${this._y}px)`;
    }
  }
  _setContextStyles(e) {
    // 设置context样式
  }
}

const $palletes = Array.prototype.slice.call(document.querySelectorAll('.palette'));
const $sliders = Array.prototype.slice.call(document.querySelectorAll('.slider'));
const $picker = document.querySelector('.color-picker');
const $indicator = document.querySelector('.color-indicator');
const contexts = $palletes.map(($context) => {
    const name = $context.getAttribute('name');
    const $dragger = $sliders.filter(element => element.getAttribute('name') === name)[0];
    return new DragContext({
      $context,
      $dragger,
      name,
      direction: 'horizontal',
      initX: 120
    });
  });
contexts.forEach((context) => {
  context.init();
});
const name = $picker.getAttribute('name');
const context = new DragContext({
  $context: $picker,
  $dragger: $indicator,
  name,
  direction: 'both'
});
context.init();
<div class="wrapper">
  <div class="color-picker" name="color">
     <div class="color-indicator" name="color"></div> 
  </div>
  <div class="preview"></div>
  <div class="hue palette" name="hue">
    <div class="slider" name="hue"></div>
  </div>
  <div class="alpha palette" name="alpha">
     <div class="slider" name="alpha"></div> 
  </div>
  <h5 class="hex result">#fff</h5>
  <h5 class="rgb result">rgba(255, 255, 255, 1)</h5>
  <h5 class="hsl result">hsla(0, 0%, 100%, 1)</h5>
</div>
<div class="target">Change my color pls!</div>
@import url(https://fonts.googleapis.com/css?family=Quicksand);

html, body {
  margin: 0;
  box-sizing: border-box;
}
body {
  background-color: #333;
  font-family: 'Quicksand', sans-serif;
  padding-top: 20px;
}

.wrapper {
  position: relative;
  background: white;
  margin: 0 auto;
  width: 200px;
  height: 265px;
  border-radius: 3px;
  border: 1px solid #666;
}

.wrapper::after {
  content: '';
  position: absolute;
  width: 10px;
  height: 10px;
  top: 260px;
  left: 94px;
  background: linear-gradient(135deg, transparent 50%, white 50%);
  border-width: 1px 1px 0 0 inherit solid;
  transform: rotate(45deg);
}

.color-picker {
  width: 200px;
  height: 120px;
  margin-bottom: 15px;
  border-radius: inherit;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1)), linear-gradient(to left, rgba(0, 0, 0, 0), rgba(255, 255, 255, 1));
  background-color: red;
  overflow: hidden;
}

.color-indicator {
  position: relative;
      left: -6px;
      top: -6px;
      width: 12px;
      height: 12px;
      transform: translate(-6px, -6px);
      border-radius: 50%;
      border: 1px solid white;
}

.preview {
  background:
     linear-gradient(45deg, rgba(0,0,0,.25) 25%, transparent 0, transparent 75%, rgba(0,0,0,.25) 0) 0 0 / 12px 12px,
     linear-gradient(45deg, rgba(0,0,0,.25) 25%, transparent 0, transparent 75%, rgba(0,0,0,.25) 0) 6px 6px / 12px 12px;
  background-color: rgba(255,0,0,0.5);
  float: left;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  margin-left: 12px;
  border: 1px solid #eee;
}
.palette {
  background: darkblue;
  width: 120px;
  height: 12px;
  margin-left: 60px;
  position: relative;
  border-radius: 1px;
  margin-bottom: 10px;
}

.hue {
  background: linear-gradient(to left,
    hsl(0, 100%, 50%) 0%,
    hsl(30, 100%, 50%) 8.33%,
    hsl(60, 100%, 50%) 16.67%,
    hsl(90, 100%, 50%) 25%,
    hsl(120, 100%, 50%) 33.33%,
    hsl(150, 100%, 50%) 41.67%,
    hsl(180, 100%, 50%) 50%,
    hsl(210, 100%, 50%) 58.33%,
    hsl(240, 100%, 50%) 66.67%,
    hsl(270, 100%, 50%) 75%,
    hsl(300, 100%, 50%) 83.33%,
    hsl(330, 100%, 50%) 91.67%,
    hsl(0, 100%, 50%) 100%);
}

.alpha {
   background:
     linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,255,1)) 0 0 / cover,
     linear-gradient(45deg, rgba(0,0,0,.25) 25%, transparent 0, transparent 75%, rgba(0,0,0,.25) 0) 0 0 / 12px 12px,
     linear-gradient(45deg, rgba(0,0,0,.25) 25%, transparent 0, transparent 75%, rgba(0,0,0,.25) 0) 6px 6px / 12px 12px;
}

.slider {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  transform: translateX(120px);
  background: rgb(248, 248, 248);
  position: relative;
  left: -8px;
  top: -2px;
  filter: drop-shadow(2px 2px 3px rgba(0,0,0,.2));
}

.result {
  color: #aaa;
  margin: 0;
  line-height: 2;
  text-align: center;
  user-select: auto;
}

.target {
  width: max-content;
  margin: 0 auto;
  padding-top: 10px;
  color: white;
  font-size: 24px;
  cursor: pointer;
}