LoginSignup
5
3

More than 1 year has passed since last update.

JavaScript input要素に対して使用可能なカラーピッカーUIを作成

Last updated at Posted at 2021-05-07

input要素で使用可能なカラーピッカーです。
グラデーションはcanvasは使わず、CSSのconic-gradient()とlinear-gradient()のみで表示しています。

設置デモ

ss4.jpg

各部機能

1 彩度/明度ピッカー
2 色相ピッカー
3 リサイズアイコン
4 RGBスライダー
5 履歴パレット
 ピッカーやスライダーの操作を行うごとに自動更新。クリックすることでカレントカラー(対象要素へのフィードバック色)へ反映されます。
6 グラデーションパレット
 左右はグラデーションの開始色、終了色をセットするボタンになっていて、クリックすることでボタンにカレントカラーがセットされます。
 中間の3段あるグラデーションは開始色~終了色のグラデーションで、上からカラーコード、色相(右回り)、色相(左回り)で補間しています。グラデーションの任意の場所を選択することでその位置の色がカレントカラーへ反映されます。

script

color_picker.js
'use strict';
{
  const
    obj = {
      color: {h: 0, s: 0, v: 1},
    },
    eventListenerManage = (function() {
      const events = {};
      let key = 1;
      return {
        add: function(target, type, listener, capture) {
          target.addEventListener(type, listener, capture);
          events[key] = {target, type, listener, capture};
          return key++;
        },
        remove: function(key) {
          if(key in events) {
            const e = events[key];
            e.target.removeEventListener(e.type, e.listener, e.capture);
            delete(events[key]);
          }
        }
      };
    }()),
    touchFlg = window.ontouchstart !== undefined ? true : false,
    pre = 'colorPicker-';

  window.addEventListener('DOMContentLoaded', function() {
    // insert picker elements
    document.querySelector('body').insertAdjacentHTML('beforeend', `
      <div class='${pre}container'>
        <div class='${pre}back'></div>
        <div class='${pre}frame'></div>
        <div class='${pre}move'></div>
        <div class='${pre}circle'></div>
        <div class='${pre}innerCircle'>
          <div class='${pre}pointer'></div>
        </div>
        <div class='${pre}circleInput'></div>
        <div class='${pre}rect'>
          <div class='${pre}pointer'>
            <div class='${pre}circle1'></div>
            <div class='${pre}circle2'></div>
          </div>
        </div>
        <div class='${pre}rectInput'></div>
        <div class='${pre}resize'></div>
        <input type='${touchFlg ? 'checkbox' : 'text'}' class='${pre}d'>
        <div class='${pre}rgbUnit'>
          <div class='${pre}rSlider'></div>
          <div class='${pre}gSlider'></div>
          <div class='${pre}bSlider'></div>
        </div>
        <div class='${pre}historyPalette'></div>
        <div class='${pre}interpolationGradation'>
          <div class='${pre}input'></div>
          <div class='${pre}left'></div>
          <div class='${pre}center'>
            <div class='${pre}rgb'></div>
            <div class='${pre}hsv0'></div>
            <div class='${pre}hsv1'></div>
            <div class='${pre}pointer'>
              <div class='${pre}shape'></div>
            </div>
          </div>
          <div class='${pre}right'></div>
        </div>
      </div>
    `);

    // elements style set
    let s = (obj.picker = document.querySelector(`div.${pre}container`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = '200px';
    s.zIndex = '10000';
    s.userSelect = s.WebkitUserSelect = 'none';

    // hue ring
    s = (obj.circle = obj.picker.querySelector(`.${pre}circle`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = '100%';
    s.borderRadius = '50%';
    const g = [];
    for(let i = 0; i <= 360; i += 60) g.push(`hsl(${i}deg 100% 50%)`);
    s.maskImage = s.WebkitMaskImage = `radial-gradient(transparent 56%, #000 56.5%)`;
    s.backgroundImage = `conic-gradient(${g.join(',')})`;

    s = (obj.circleInput = obj.picker.querySelector(`.${pre}circleInput`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = '104%';
    s.top = s.left= '-2%';
    s.borderRadius = '50%';

    s = (obj.innerCircle = obj.picker.querySelector(`.${pre}innerCircle`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = '80%';
    s.top = s.left = '10%';
    s.backgroundColor = '#fff6';
    s.borderRadius = '50%';

    s = (obj.circlePointer = obj.innerCircle.querySelector(`.${pre}pointer`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = '6%';
    s.height = '10%';
    s.top = '-11.4%';
    s.left = '47%';
    s.border = '2px #000 solid';
    s.borderRadius = '20%';

    // color rectangle
    s = (obj.rect = obj.picker.querySelector(`.${pre}rect`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = (75 / Math.sqrt(2)) + '%';
    s.top = s.left = (50 - (75 / Math.sqrt(2) / 2)) + '%';
    s.backgroundColor = '#f00';
    s.backgroundImage = 'linear-gradient(to top, #000, #0000), linear-gradient(to left, #fff0, #fff)';

    s = (obj.rectInput = obj.picker.querySelector(`.${pre}rectInput`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = (80 / Math.sqrt(2)) + '%';
    s.top = s.left = (50 - (80 / Math.sqrt(2) / 2)) + '%';

    s = (obj.rectPointer = obj.rect.querySelector(`.${pre}pointer`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = '12%';
    s.top = s.left = '0';

    s = (obj.rectPointerCircle1 = obj.rectPointer.querySelector(`.${pre}circle1`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.top = s.left = '-50%';
    s.width = s.height = '100%';
    s.border = '1px #fff solid';
    s.borderRadius = '50%';

    s = (obj.rectPointerCircle2 = obj.rectPointer.querySelector(`.${pre}circle2`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.top = s.left = (-50 + (100 - 80) / 2) + '%';
    s.width = s.height = '80%';
    s.backgroundColor = '#fff';
    s.borderRadius = '50%';
    s.border = '1px #000 solid';

    // resize icon
    s = (obj.resize = obj.picker.querySelector(`.${pre}resize`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.borderRight = s.borderBottom = '1px #888 solid';
    s.width = s.height = '12%';
    s.right = s.bottom = '0';
    s.cursor = 'nwse-resize';

    // move icon
    s = (obj.move = obj.picker.querySelector(`.${pre}move`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = s.height = '100%';
    s.cursor = 'move';

    // dummy object
    s = (obj.d = obj.picker.querySelector(`.${pre}d`)).style;
    s.position = 'fixed';
    s.left = s.top = '-500px';

    s = (obj.back = obj.picker.querySelector(`.${pre}back`)).style;
    s.position = 'fixed';
    s.width = s.height = '100%';
    s.left = s.top = '0';

    s = (obj.frame = obj.picker.querySelector(`.${pre}frame`)).style;
    s.position = 'absolute';
    s.width = s.height = '110%';
    s.left = s.top = '-5%';

    // RGB slider
    s = (obj.rgbUnit = obj.picker.querySelector(`.${pre}rgbUnit`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = '100%';
    s.height = '30%';
    s.left = '0';
    s.top = '100%';

    for(let i = 0; i < 3; i++) {
      const c = 'rgb'[i];
      s = (obj[`${c}Slider`] = obj.picker.querySelector(`.${pre}${c}Slider`)).style;
      s.position = 'absolute';
      s.boxSizing = 'border-box';
      s.width = '90%';
      s.left = '5%';
      s.height = '22%';
      s.zIndex = '-1';
      s.userSelect = 'none';
      s.paddingLeft = '2%';
      s.top = `${33.33 * i + 33.33 - 22}%`;
      s.borderBottom = `1px solid #${['f00', '0f0', '00f'][i]}`;
      obj[`${c}Slider`].appendChild(document.createElement('div'));
      s = (obj[`${c}Slider`].child = obj[`${c}Slider`].lastChild).style;
      s.color = '#fff';
      s.mixBlendMode = 'difference';
    }

    // history palette
    s = (obj.historyPalette = obj.picker.querySelector(`.${pre}historyPalette`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.display = 'flex';
    s.justifyContent = 'space-between';
    s.width = '100%';
    s.height = '16%';
    s.left = '0';

    obj.historyCell = [];
    for(let i = 0; i < 8; i++) {
      obj.historyPalette.appendChild(document.createElement('div'));
      s = (obj.historyCell[i] = obj.historyPalette.lastChild).style;
      s.position = 'relative';
      s.boxSizing = 'border-box';
      s.border = '1px #eee solid';
      s.width = '100%';
      s.height = '75%';
      s.top = '25%';
      s.backgroundColor = '#fff';
    }

    // gradation unit
    s = (obj.gradation = obj.picker.querySelector(`.${pre}interpolationGradation`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.display = 'flex';
    s.justifyContent = 'space-between';
    s.width = '100%';
    s.height = '35%';
    s.left = '0';

    s = (obj.gradationInput = obj.gradation.querySelector(`.${pre}input`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.width = '100%';
    s.height = '90%';
    s.top = '10%';

    s = (obj.gradationLeft = obj.gradation.querySelector(`.${pre}left`)).style;
    s.position = 'relative';
    s.boxSizing = 'border-box';
    s.width = '10%';
    s.height = '90%';
    s.top = '10%';
    s.border = '1px #ddd solid';
    s.cursor = 'pointer';
    obj.gradationColorLeft = {...obj.color};
    s.backgroundColor = hsv2rgb(obj.color.h, obj.color.s, obj.color.v).cc;

    s = (obj.gradationCenter = obj.gradation.querySelector(`.${pre}center`)).style;
    s.position = 'relative';
    s.boxSizing = 'border-box';
    s.width = '80%';
    s.height = '90%';
    s.top = '10%';
    s.zIndex = '-1';

    s = (obj.gradationRight = obj.gradation.querySelector(`.${pre}right`)).style;
    s.position = 'relative';
    s.boxSizing = 'border-box';
    s.width = '10%';
    s.height = '90%';
    s.top = '10%';
    s.border = '1px #ddd solid';
    s.cursor = 'pointer';
    obj.gradationColorRight = {...obj.color};
    s.backgroundColor = hsv2rgb(obj.color.h, obj.color.s, obj.color.v).cc;

    for(let i = 0; i < 3; i++) {
      s = (obj[`gradation${['Rgb', 'Hsv0', 'Hsv1'][i]}`] =
        obj.gradationCenter.querySelector(`.${pre}${['rgb', 'hsv0', 'hsv1'][i]}`)).style;
      s.position = 'relative';
      s.boxSizing = 'border-box';
      s.width = '100%';
      s.height = '33.33%';
      s.border = '1px #ddd solid';
    }

    s = (obj.gradationPointer = obj.gradationCenter.querySelector(`.${pre}pointer`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.top = s.left = '0';
    s.width = '5px';
    s.height = '33.33%';
    s.opacity = '0';

    s = (obj.gradationPointerShape = obj.gradationPointer.querySelector(`.${pre}shape`)).style;
    s.position = 'absolute';
    s.boxSizing = 'border-box';
    s.top = '0';
    s.left = '-1px';
    s.width = '3px';
    s.height = '100%';
    s.backgroundColor = '#fff';
    s.borderLeft = s.borderRight = '1px #000 solid';

    setGradation();
    obj.picker.style.display = 'none';

    // picker eventListener set
    const ev = {
      mousedown: touchFlg ? 'touchstart' : 'mousedown',
      mousemove: touchFlg ? 'touchmove' : 'mousemove',
      mouseup: touchFlg ? 'touchend' : 'mouseup',
    };

    obj.circleInput.addEventListener(ev.mousedown, function(e) {
      obj.d.focus();
      obj.circleInput.style.zIndex = '99';
      obj.t = 'circle';
      circleInput(e);
      obj.rect.style.backgroundColor = `hsl(${obj.color.h}deg 100% 50%)`;
      const prevColor = obj.historyCell[0].style.backgroundColor;
      feedbackValue(obj.p);
      historyPush(prevColor);
      enabledPreventDefault();
    });

    obj.rectInput.addEventListener(ev.mousedown, function(e) {
      obj.d.focus();
      obj.t = 'rect';
      rectInput(e);
      const prevColor = obj.historyCell[0].style.backgroundColor;
      feedbackValue(obj.p);
      historyPush(prevColor);
      enabledPreventDefault();
    });

    obj.resize.addEventListener(ev.mousedown, function() {
      obj.d.focus();
      obj.t = 'resize';
      enabledPreventDefault();
    });

    obj.move.addEventListener(ev.mousedown, function(e) {
      obj.d.focus();
      obj.t = 'move';
      obj.offsetX = touchFlg ? e.touches[0].pageX - obj.picker.offsetLeft : e.layerX;
      obj.offsetY = touchFlg ? e.touches[0].pageY - obj.picker.offsetTop : e.layerY;
      enabledPreventDefault();
    });

    obj.rgbUnit.addEventListener(ev.mousedown, function(e) {
      obj.d.focus();
      const
        y = touchFlg ? e.touches[0].pageY - (obj.picker.offsetTop + this.offsetTop) : e.layerY,
        n = Math.floor(y / this.clientHeight * 3),
        prevColor = obj.historyCell[0].style.backgroundColor;
      obj.t = `slider${n}`;
      sliderInput(obj[`${'rgb'[n]}Slider`], e, n);
      historyPush(prevColor);
      enabledPreventDefault();
    });

    obj.rgbUnit.addEventListener(ev.mousemove, function(e) {
      const m = obj.t.match(/slider(\d)$/);
      if(m === null || m[1] === undefined) return;
      sliderInput(obj[`${'rgb'[+m[1]]}Slider`], e, +m[1]);
    });

    for(let i = 0; i < 8; i++) {
      obj.historyCell[i].addEventListener(ev.mousedown, function() {
        obj.t = 'history';
        const rgb = this.style.backgroundColor.match(/\d+/g);
        if(rgb !== null) {
          const hsv = rgb2hsv(+rgb[0], +rgb[1], +rgb[2]);
          obj.color = {...hsv};
          obj.rectPointer.style.left = Math.floor(obj.rect.clientWidth * hsv.s) + 'px';
          obj.rectPointer.style.top = Math.floor(obj.rect.clientHeight * (1 - hsv.v)) + 'px';
          obj.rect.style.backgroundColor = `hsl(${hsv.h}deg 100% 50%)`;
          obj.innerCircle.style.transform = `rotate(${hsv.h}deg)`;
          obj.circlePointer.style.borderColor = `hsl(${180 + hsv.h}, 100%, 50%)`;
          feedbackValue(obj.p, true);
        }
        enabledPreventDefault();
      });
    }

    obj.circleInput.addEventListener(ev.mousemove, enabledPreventDefault);
    obj.rgbUnit.addEventListener(ev.mousemove, enabledPreventDefault);
    obj.historyPalette.addEventListener(ev.mousemove, enabledPreventDefault);
    obj.gradation.addEventListener(ev.mousemove, enabledPreventDefault);

    window.addEventListener(ev.mousemove, function(e) {
      if(obj.t === 'resize') {
        const
          e_ = touchFlg ? e.touches[0] : e,
          h = e_.pageX - obj.picker.offsetLeft,
          v = e_.pageY - obj.picker.offsetTop,
          size = Math.max(100, Math.max(h, v));
        obj.picker.style.width = obj.picker.style.height = size + 'px';
        obj.rectPointer.style.left = Math.floor(obj.rect.clientWidth * obj.color.s) + 'px';
        obj.rectPointer.style.top = Math.floor(obj.rect.clientHeight * (1 - obj.color.v)) + 'px';
        setRgbSliderValue(true);
      }
      else if(obj.t === 'move') {
        const
          e_ = touchFlg ? e.touches[0] : e,
          x = e_.pageX - obj.offsetX,
          y = e_.pageY - obj.offsetY;
        obj.picker.style.left = x + 'px';
        obj.picker.style.top = y + 'px';
      }
      else if(obj.t === 'circle') {
        circleInput(e);
        obj.rect.style.backgroundColor = `hsl(${obj.color.h}deg 100% 50%)`;
        feedbackValue(obj.p);
      }
      else if(obj.t === 'rect') {
        rectInput(e);
        feedbackValue(obj.p);
      }
    });

    window.addEventListener(ev.mouseup, function() {
      setTimeout(function() {
        obj.t = '';
        obj.circleInput.style.zIndex = '';
        obj.gradationInput.style.zIndex = '';
        obj.gradationPointer.style.opacity = '0';
        if(obj.touchmoveId) {
          eventListenerManage.remove(obj.touchmoveId);
          obj.touchmoveId = 0;
        }
      });
      if(obj.t !== undefined && obj.t !== '' || obj.windowScroll === 1) return;
      obj.picker.style.display = 'none';
      if(obj.p !== undefined) {
        delete obj.p;
        for(const e of elements) e.dataset.colorpickerDisplay = 0;
      }
    });

    window.addEventListener(ev.mousedown, function() {
      obj.windowScroll = 0;
    });

    window.addEventListener('touchmove', function() {
      obj.windowScroll = 1;
    });

    obj.historyPalette.addEventListener(ev.mousedown, function() {
      obj.t = 'historyPalette';
    });

    obj.gradation.addEventListener(ev.mousedown, function() {
      obj.t = 'gradation';
    });

    obj.gradationLeft.addEventListener(ev.mousedown, function() {
      obj.d.focus();
      obj.t = 'gradationLeft';
      enabledPreventDefault();
      const c = obj.color;
      this.style.backgroundColor = hsv2rgb(c.h, c.s, c.v).cc;
      obj.gradationColorLeft = {...c};
      setGradation();
    });

    obj.gradationRight.addEventListener(ev.mousedown, function() {
      obj.d.focus();
      obj.t = 'gradationRight';
      enabledPreventDefault();
      const c = obj.color;
      this.style.backgroundColor = hsv2rgb(c.h, c.s, c.v).cc;
      obj.gradationColorRight = {...c};
      setGradation();
    });

    obj.gradationInput.addEventListener(ev.mousedown, function(e) {
      obj.d.focus();
      obj.t = 'gradation';
      enabledPreventDefault();
      obj.gradationInput.style.zIndex = '99';

      const y = touchFlg ?
        e.touches[0].pageY - (obj.picker.offsetTop + obj.gradation.offsetTop + this.offsetTop) : e.layerY;
      obj.gradationSelectNumber = Math.min(Math.floor(y / this.clientHeight * 3), 2);
      obj.gradationPointer.style.opacity = '1';
      obj.gradationPointer.style.top = (obj.gradationSelectNumber * this.clientHeight / 3) + 'px';

      const centor = obj.gradationCenter;
      const prevColor = obj.historyCell[0].style.backgroundColor;
      obj.historyCell[0].style.backgroundColor = '';
      historyPush(prevColor);
      pickupGradationColor(e);
    });

    obj.gradationInput.addEventListener(ev.mousemove, function(e) {
      if(obj.t === '') return;
      pickupGradationColor(e);
    });

    // target elements eventListener set
    const elements = document.querySelectorAll('input[data-colorpicker]');

    let i = 1;
    for(const e of elements) {
      e.dataset.colorpickerId = i++;
      e.addEventListener('click', function() {
        if(e.dataset.colorpickerDisplay & 1) {
          obj.picker.style.display = 'none';
          e.dataset.colorpickerDisplay = 0;
        }
        else {
          const d = e.dataset.colorpicker.toLowerCase();
          const size = d.match(/size[^:]*:\s*([.\d]+)/);
          obj.picker.style.width = obj.picker.style.height =
            (size !== null ? Math.max(100, size[1]) : 200) + 'px';

          displayPicker(e);
          let t = [];
          t = d.match(/rgb-slider-disabled[^:]*:\s*([\w]+)/);
          obj.rgbUnit.style.display =
            (t !== null && (+t[1] || t[1] === 'true')) ? 'none' : 'block';
          t = d.match(/history-palette-disabled[^:]*:\s*([\w]+)/);
          obj.historyPalette.style.display =
            (t !== null && (+t[1] || t[1] === 'true')) ? 'none' : 'flex';
          t = d.match(/gradation-palette-disabled[^:]*:\s*([\w]+)/);
          obj.gradation.style.display =
            (t !== null && (+t[1] || t[1] === 'true')) ? 'none' : 'flex';
          obj.historyPalette.style.top =
            (100 + obj.rgbUnit.clientHeight / obj.picker.clientHeight * 100) + '%';
          obj.gradation.style.top =
            (100 + (obj.rgbUnit.clientHeight + obj.historyPalette.clientHeight) / obj.picker.clientHeight * 100) + '%';

          obj.frame.style.backgroundColor = '#fffc';
          obj.frame.style.zIndex = '-1';
          obj.frame.style.borderRadius = '5px';
          obj.frame.style.boxShadow = '0 0 4px 4px #0001';
          obj.frame.style.height = (10 + (obj.picker.clientHeight + obj.rgbUnit.clientHeight +
             obj.historyPalette.clientHeight + obj.gradation.clientHeight) / obj.picker.clientHeight * 100) + '%';
          obj.rectPointer.style.left = obj.rectPointer.style.top = '0';
          obj.rect.style.backgroundColor = '#f00';
          obj.innerCircle.style.transform = 'rotate(0deg)';
          obj.rectPointerCircle2.style.backgroundColor = '#fff';
          obj.d.style.color = e.value;
          const rgb = obj.d.style.color.match(/\d+/g);
          if(rgb !== null) {
            rgb.splice(3);
            const hsv = rgb2hsv(+rgb[0], +rgb[1], +rgb[2]);
            obj.innerCircle.style.transform = `rotate(${hsv.h}deg)`;
            obj.rectPointer.style.left = Math.floor(obj.rect.clientWidth * hsv.s) + 'px';
            obj.rectPointer.style.top = Math.floor(obj.rect.clientHeight * (1 - hsv.v)) + 'px';
            obj.rect.style.backgroundColor = `hsl(${hsv.h}deg 100% 50%)`;
            obj.color = {...hsv};
            obj.circlePointer.style.borderColor = `hsl(${180 + hsv.h}, 100%, 50%)`;
            obj.rectPointerCircle2.style.backgroundColor = `rgb(${rgb.join(',')})`;
          }
          e.dataset.colorpickerDisplay = 1;
          if(obj.p !== undefined) {
            if(e.dataset.colorpickerId !== obj.p.dataset.colorpickerId)
              obj.p.dataset.colorpickerDisplay = 0;
          }
          setRgbSliderValue(true);
          historyPush();
          obj.p = e;
          disabledDefaultPicker(this);
        }
      });
    }
  });

  // functions
  function hsv2rgb(h, s, v) {
    const
      a = h / 60,
      [r, g, b] = [5, 3, 1].map(
        i => Math.round(
          (v - Math.max(0, Math.min(1, 2 - Math.abs(2 - (a + i) % 6))) * s * v) * 255
        ));
    return {cc: '#' + (r << 16 | g << 8 | b).toString(16).padStart(6, '0'), rgb: [r, g, b], r, g, b};
  }

  function rgb2hsv(r, g, b) {
    const
      v = Math.max(r, g, b), d = v - Math.min(r, g, b),
      s = v ? d / v : 0, a = [r, g, b, r, g], i = a.indexOf(v),
      h = s ? (((a[i + 1] - a[i + 2]) / d + i * 2 + 6) % 6) * 60 : 0;
    return {h, s, v: v / 255};
  }

  function displayPicker(e) {
    obj.picker.style.display = 'block';
    const x = e.offsetLeft + obj.picker.clientWidth <= window.innerWidth ||
      window.innerWidth < obj.picker.clientWidth ? e.offsetLeft : window.innerWidth - obj.picker.clientWidth;
    obj.picker.style.left = 8 + x + 'px';
    obj.picker.style.top = (16 + e.offsetTop + e.clientHeight) + 'px';
    obj.color = {h: 0, s: 0, v: 1};
    obj.circlePointer.style.borderColor = `hsl(${180}, 100%, 50%)`;
    setTimeout(function() { obj.d.focus();});
  }

  function circleInput(e) {
    const
      h = obj.circleInput.clientWidth,
      v = obj.circleInput.clientHeight,
      e_ = touchFlg ? e.touches[0] : e,
      x = e_.pageX - obj.picker.offsetLeft - obj.circleInput.offsetLeft,
      y = e_.pageY - obj.picker.offsetTop - obj.circleInput.offsetTop,
      at = Math.atan2(v / 2 - y , h / 2 - x),
      k = (at * 180 / Math.PI + 270) % 360;
    obj.innerCircle.style.transform = `rotate(${k}deg)`;
    obj.color.h = k;
    obj.circlePointer.style.borderColor = `hsl(${180 + k}, 100%, 50%)`;
  }

  function rectInput(e) {
    const
      h = obj.rect.clientWidth,
      v = obj.rect.clientHeight,
      e_ = touchFlg ? e.touches[0] : e,
      x_ = (e_.pageX - obj.rectInput.offsetLeft - obj.picker.offsetLeft) - (obj.rectInput.clientWidth - h) / 2,
      y_ = (e_.pageY - obj.rectInput.offsetTop - obj.picker.offsetTop) - (obj.rectInput.clientHeight - v) / 2,
      x = x_ < 0 ? 0 : x_ > h ? h : x_,
      y = y_ < 0 ? 0 : y_ > v ? v : y_;
    obj.rectPointer.style.left = x + 'px';
    obj.rectPointer.style.top = y + 'px';
    obj.color.s = x / h;
    obj.color.v = 1 - y / v;
  }

  function sliderInput(o, e, ch) {
    const
      h = o.clientWidth,
      x = (touchFlg ? e.touches[0].pageX - obj.picker.offsetLeft : e.layerX) - o.offsetLeft,
      c = obj.color,
      rgb = hsv2rgb(c.h, c.s, c.v).rgb;
    rgb[ch] = (x < 0 ? 0 : x > h ? h : x) / h * 255;
    const hsv = rgb2hsv(rgb[0], rgb[1], rgb[2]);
    obj.color = {...hsv};
    obj.rectPointer.style.left = Math.floor(obj.rect.clientWidth * hsv.s) + 'px';
    obj.rectPointer.style.top = Math.floor(obj.rect.clientHeight * (1 - hsv.v)) + 'px';
    obj.rect.style.backgroundColor = `hsl(${hsv.h}deg 100% 50%)`;
    obj.innerCircle.style.transform = `rotate(${hsv.h}deg)`;
    obj.circlePointer.style.borderColor = `hsl(${180 + hsv.h}, 100%, 50%)`;
    setRgbSliderValue();
    feedbackValue(obj.p);
  }

  function feedbackValue(e, notUpdateHistory) {
    const c = obj.color;
    e.value = hsv2rgb(c.h, c.s, c.v).cc;
    if(e.type !== 'color') {
      e.style.backgroundColor = e.value;
      const rgb = e.style.backgroundColor.match(/\d+/g);
      if(rgb !== null) {
        const l = (rgb[0] * 2 + rgb[1] * 4 + +rgb[2]) / 3;
        e.style.color = l > 300 ? '#000' : '#fff';
      }
    }
    obj.rectPointerCircle2.style.backgroundColor = e.value;
    setRgbSliderValue();
    if(notUpdateHistory === undefined && notUpdateHistory !== true)
      obj.historyCell[0].style.backgroundColor = obj.historyCell[0].dataset.colorCode = e.value;
  }

  function setRgbSliderValue(f) {
    const
      c = obj.color,
      rgb = hsv2rgb(c.h, c.s, c.v).rgb,
      fontsize = f === true ? (obj.rgbUnit.clientWidth / 20) + 'px' : '';
    for(let i = 0; i < 3; i++) {
      const ch = [0, 0, 0];
      ch[i] = rgb[i];
      obj[`${'rgb'[i]}Slider`].style.backgroundImage =
        `linear-gradient(to right, #000, rgb(${ch.join(',')}) ${rgb[i] / 2.55}%, #fff8 0%)`;
      obj[`${'rgb'[i]}Slider`].child.textContent = rgb[i];
      if(fontsize) obj[`${'rgb'[i]}Slider`].child.style.fontSize = fontsize;
    }
  }

  function historyPush(prevColor) {
    const
      c = obj.color,
      currentColorcode = hsv2rgb(c.h, c.s, c.v).cc;
    if(obj.historyCell[0].dataset.colorCode !== undefined &&
      obj.historyCell[0].dataset.colorCode === currentColorcode && prevColor === undefined) return;
    if(prevColor !== undefined && prevColor === obj.historyCell[0].style.backgroundColor) return;
    for(let i = 0; i < 7; i++)
      obj.historyCell[7 - i].style.backgroundColor = obj.historyCell[6 - i].style.backgroundColor;
    if(prevColor !== undefined) obj.historyCell[1].style.backgroundColor = prevColor;
    obj.historyCell[0].style.backgroundColor = obj.historyCell[0].dataset.colorCode = currentColorcode;
  }

  function enabledPreventDefault() {
    if(obj.touchmoveId) return;
    obj.touchmoveId = eventListenerManage.add(obj.picker, 'touchmove', function(e) {
      e.preventDefault();
    });
  }

  function setGradation() {
    const
      c0 = obj.gradationColorLeft,
      c1 = obj.gradationColorRight,
      hR = getHuePosition(c0.h, c1.h),
      hL = getHuePosition(c0.h, c1.h, 1),
      rgb0 = hsv2rgb(c0.h, c0.s, c0.v).rgb,
      rgb1 = hsv2rgb(c1.h, c1.s, c1.v).rgb,
      n = 12, g0 = [], g1 = [], g2 = [];
    for(let i = 0; i < n; i++) {
      const
        is = c0.s + (c1.s - c0.s) / n * i,
        iv = c0.v + (c1.v - c0.v) / n * i;
      g1.push(hsv2rgb(hR[0] + (hR[1] - hR[0]) / n * i, is, iv).cc);
      g2.push(hsv2rgb(hL[0] + (hL[1] - hL[0]) / n * i, is, iv).cc);
    }
    g0.push(`rgb(${rgb0.join(',')})`);
    const rightColor = `rgb(${rgb1.join(',')})`;
    g0.push(rightColor);
    g1.push(rightColor);
    g2.push(rightColor);
    obj.gradationRgb.style.backgroundImage = `linear-gradient(to right, ${g0.join(',')})`;
    obj.gradationHsv0.style.backgroundImage = `linear-gradient(to right, ${g1.join(',')})`;
    obj.gradationHsv1.style.backgroundImage = `linear-gradient(to right, ${g2.join(',')})`;
  }

  function pickupGradationColor(e) {
    const
      centor = obj.gradationCenter,
      n = obj.gradationSelectNumber,
      x = touchFlg ?
        e.touches[0].pageX - (obj.picker.offsetLeft + obj.gradationCenter.offsetLeft) :
        e.layerX - centor.offsetLeft,
      value = Math.max(0, Math.min(1, x / centor.clientWidth)),
      c0 = obj.gradationColorLeft,
      c1 = obj.gradationColorRight,
      rgb0 = hsv2rgb(c0.h, c0.s, c0.v).rgb,
      rgb1 = hsv2rgb(c1.h, c1.s, c1.v).rgb;
    obj.gradationPointer.style.left = (value * centor.clientWidth) + 'px';
    const hsv = {h: 0, s: 0, v: 1};
    if(n === 0) {
      const c = rgb2hsv(
        rgb0[0] + (rgb1[0] - rgb0[0]) * value,
        rgb0[1] + (rgb1[1] - rgb0[1]) * value,
        rgb0[2] + (rgb1[2] - rgb0[2]) * value);
      [hsv.h, hsv.s, hsv.v] = [c.h, c.s, c.v];
    }
    else {
      const hP = getHuePosition(c0.h, c1.h, n === 1 ? undefined : 1);
      hsv.h = hP[0] + (hP[1] - hP[0]) * value;
      hsv.s = c0.s + (c1.s - c0.s) * value;
      hsv.v = c0.v + (c1.v - c0.v) * value;
    }
    obj.color = {...hsv};
    obj.rectPointer.style.left = Math.floor(obj.rect.clientWidth * hsv.s) + 'px';
    obj.rectPointer.style.top = Math.floor(obj.rect.clientHeight * (1 - hsv.v)) + 'px';
    obj.rect.style.backgroundColor = `hsl(${hsv.h}deg 100% 50%)`;
    obj.innerCircle.style.transform = `rotate(${hsv.h}deg)`;
    obj.circlePointer.style.borderColor = `hsl(${180 + hsv.h}, 100%, 50%)`;
    obj.historyCell[0].style.backgroundColor = hsv2rgb(hsv.h, hsv.s, hsv.v).cc;
    feedbackValue(obj.p, true);
  }

  function disabledDefaultPicker(e) {
    const cType = e.type;
    const cn = e.cloneNode();
    e.before(cn);
    e.type = 'hidden';
    setTimeout(function() {
      e.value = cn.value;
      e.type = cType;
      cn.remove();
    });
  }

  function getHuePosition(start, end, direction) {
    start %= 360;
    end %= 360;
    if(direction !== undefined && direction) {
      if(start < end) start += 360;
    }
    else {
      if(start > end) end += 360;
    }
    return [start, end];
  }
}

使用方法

scriptタグでcolor_picker.jsを読み込み、対象input要素にdata-colorpicker属性を付加することで適用できます。

<script src='color_picker.js'></script>
<input type='text' data-colorpicker>
<input type='text' data-colorpicker='size:300'>

以下のオプションが使用できます。
size: サイズ
 ピッカーの初期サイズ
 単位はpixel、最小値は100、無指定時のデフォルトは200です。

rgb-slider-disabled: true
 RGBスライダー非表示

history-palette-disabled: true
 履歴パレット非表示

gradation-palette-disabled: true
 グラデーションパレット非表示

適用input要素のtypeはとくに制限していませんが、value値としてカラーコードを指定できない要素では期待する動作にはならないと思います。

サンプル

<!DOCTYPE html>
<html lang='ja'>
  <head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width,initial-scale=1'>
    <script src='color_picker.js'></script>
  </head>
  <body>
    type=text<br>
    <input type='text' data-colorpicker value='#ffff00'>
    <input type='text' data-colorpicker>
    <input type='text' data-colorpicker='size:300; rgb-slider-disabled:true; gradation-palette-disabled:true;'>
    <input type='text' data-colorpicker='size:250; rgb-slider-disabled:true; history-palette-disabled:true; gradation-palette-disabled:true;'>
    <hr>
    type=button<br>
    <input type='button' style='width:70px' data-colorpicker><hr>
    type=color<br>
    <input type='color' value='#ff0000' data-colorpicker><br>
    <input type='color' value='#ff0000'> 本UIなし(各ブラウザのデフォルトピッカー)
  </body>
</html>

設置デモ
スマホ、PC両対応(IE除く)


関連記事 JavaScript / PHP:RGB HSV HSL 相互変換

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3