乘风原创程序

  • js实现拾色器插件(ColorPicker)
  • 2020/6/23 10:17:48
  • 对一个前端来说,颜色选择的插件肯定不陌生,许多小伙伴对这类插件的实现可能会比较好奇,这里奉上原生js版本的拾色器。

    效果图:

    讲下实现方式:

    1.颜色除了rgb跟十六进制的表现外,还有一个hsv的表现形式。h(hue)是色相,值域是0度到360度,这个值控制的是你看到的是什么颜色,通俗点讲就是红橙黄绿...;s(saturation)是饱和度,值域是0到1,这个值控制颜色的鲜艳程度,可以理解为大红跟淡红的差别;v(value)可以理解为亮度,值域也是0到1。

    2.rgb颜色跟hsv颜色的相互转化有专门的公式,可自行去百度了解下

    3.面向对象的编程方式公认为易扩展,高复用。

    整个目录结构如下:

    colorpicker
      --css
        --common.css(样式)
      --js
        --colorpicker.js(插件主体)
        --event.js(简易的发布者-订阅者实现)
        --inherite.js(继承手段,寄生组合式)
      colorpicker.html

    使用说明:

    插件目前只支持传入h、s、v值来初始化颜色,若什么都不传,默认h、s、v都为0,实例化colorpicker构造函数后,通过select方法初始化;目前只暴露了两个回调接口onhchange(色相改变触发)、onsvchange(饱和度或亮度改变触发):

    var aa = new colorpicker();
    aa.select(
     // {
     //  h: 120,
     //  s: 1,
     //  b: 1
     // }
    );
    aa.onhchange = function(e) {};
    aa.onsvchange = function(e) {};

    代码如下:

    colorpicker.html:

    <!doctype html>
    <!--[if lt ie 7]>  <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
    <!--[if ie 7]>   <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
    <!--[if ie 8]>   <html class="no-js lt-ie9"> <![endif]-->
    <!--[if gt ie 8]><!--> <html class="no-js"> <!--<![endif]-->
     <head>
      <meta charset="utf-8">
      <meta http-equiv="x-ua-compatible" content="ie=edge">
      <title>color picker</title>
      <meta name="description" content="">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="stylesheet" href="./css/common.css" rel="external nofollow" >
     </head>
     <body>
     </body>
     <script type="text/javascript" src="js/event.js"></script>
     <script type="text/javascript" src="js/inherite.js"></script>
     <script type="text/javascript" src="js/colorpicker.js"></script>
    </html>

    common.css:

    body {
     height: calc(100vh);
     overflow: hidden;
     background: gray;
    }
    .color-picker-container {
     border: 0;
     width: 300px;
     margin: 0 auto;
    }
    .color-picker-container .val-container {
     border: 1px solid silver;
     text-align: center;
     line-height: 30px;
     font-weight: bold;
    }
    .color-picker-container .val-container .val {
     width: 100%;
     border: 0;
     line-height: 32px;
     text-align: center;
     font-weight: bold;
    }
    .color-picker-container .picker-container {
     position: relative;
     width: 100%;
     margin-top: 15px;
    }
     
    .color-picker-container .picker-container .pointer {
     position: absolute;
     width: 14px;
     height: 14px;
     border-radius: 50%;
     border: 3px solid #ffffff;
     margin-left: -9px;
     margin-top: -9px;
     top: 255px;
     left: 0;
     cursor: pointer;
    }
     
    .color-picker-container .picker-container .saturation-range {
     float: left;
     width: 255px;
     height: 255px;
     /* background-color: rgba(0, 0, 0, .2); */
     box-shadow: 1px 1px 5px rgba(0, 0, 0, .6);
    }
     
    .color-picker-container .picker-container .saturation-range .cover {
     width: 100%;
     height: 100%;
     background: -webkit-linear-gradient(top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) 100%);
     background: linear-gradient(top, rgb(0, 0, 0, 0) 0%, rgb(0, 0, 0) 100%);
    }
     
    .color-picker-container .picker-container .hue-range {
     float: right;
     width: 30px;
     height: 255px;
     box-shadow: 1px 1px 5px rgba(0, 0, 0, .6);
     background: -webkit-linear-gradient(top,
      rgb(255, 0, 0) 0%,
      rgb(255, 0, 255) 17%, 
      rgb(0, 0, 255) 34%, 
      rgb(0, 255, 255) 50%, 
      rgb(0, 255, 0) 67%, 
      rgb(255, 255, 0) 84%, 
      rgb(255, 0, 0) 100%);
     background: linear-gradient(top,
      rgb(255, 0, 0) 0%,
      rgb(255, 0, 255) 17%, 
      rgb(0, 0, 255) 34%, 
      rgb(0, 255, 255) 50%, 
      rgb(0, 255, 0) 67%, 
      rgb(255, 255, 0) 84%, 
      rgb(255, 0, 0) 100%);
    }
     
    .color-picker-container .picker-container .hue-range .cursor {
     position: relative;
     width: 44px;
     margin-top: -11px;
     top: 255px;
     cursor: n-resize;
    }
     
    .color-picker-container .picker-container .hue-range .cursor::before {
     content: '';
     display: inline-block;
     width: 0;
     height: 0;
     border-style: solid;
     border-top-width: 5px;
     border-right-width: 0;
     border-bottom-width: 5px;
     border-left-width: 7px;
     border-top-color: transparent;
     border-bottom-color: transparent;
     border-left-color: rgba(0, 0, 0, .5);
     margin-left: -8px;
    }
     
    .color-picker-container .picker-container .hue-range .cursor::after {
     content: '';
     display: inline-block;
     width: 0;
     height: 0;
     border-style: solid;
     border-top-width: 5px;
     border-right-width: 7px;
     border-bottom-width: 5px;
     border-left-width: 0;
     border-top-color: transparent;
     border-bottom-color: transparent;
     border-right-color: rgba(0, 0, 0, .5);
     margin-left: 33px;
    }
     
    .color-picker-container .picker-container::after {
     content: '';
     display: block;
     clear: both;
     line-height: 0;
     visibility: hidden;
    }

    event.js:

    function event() {
     this.bindevent = [];
    }
    event.prototype.addevent = function(name, callback) {
     if(typeof callback !== 'function') return;
     var bexistevent = false;
     var untieevent = function() {
      if(window.removeeventlistener) {
       this.element.removeeventlistener(name, callback);
      } else if(window.detachevent) {
       this.element.detachevent('on' + name, callback);
      } else {
       this.element['on' + name] = null;
      }
     };
     for(var i = 0, len = this.bindevent.length; i < len; i++) {
      if(this.bindevent[i].name == name) {
       this.removeevent(name);
       this.bindevent[i].untie = untieevent;
       this.bindevent[i].event = callback;
       bexistevent = true;
       break;
      }
     }
     if(window.addeventlistener) {
      this.element.addeventlistener(name, callback);
     } else if(window.attachevent) {
      this.element.attachevent('on' + name, callback);
     } else {
      this.element['on' + name] = callback;
     }
     if(!bexistevent) {
      this.bindevent.push({
       name: name,
       event: callback,
       untie: function() {
        if(window.removeeventlistener) {
         this.element.removeeventlistener(name, callback);
        } else if(window.detachevent) {
         this.element.detachevent('on' + name, callback);
        } else {
         this.element['on' + name] = null;
        }
       }
      });
     }
    }
    event.prototype.removeevent = function(name) {
     if(typeof name === 'undefined' || name === '') return;
     // 从已绑定事件列表中剔除
     for(var i = 0, len = this.bindevent.length; i < len; i++) {
      if(this.bindevent[i].name == name) {
       this.bindevent[i].untie.call(this);  // 移除绑定事件
       this.bindevent.splice(i, 1); // 从事件列表删除
       break;
      }
     }
    }
    event.prototype.triggerevent = function(name) {
     var callback = null;
     for(var i = 0, len = this.bindevent.length; i < len; i++) {
      if(this.bindevent[i].name === name) {
       callback = this.bindevent[i].event;
      }
     }
     if(typeof callback === 'function') {
      callback.apply(this, [].slice.call(arguments).slice(1));
     }
    }

    inherite.js:

    function inheritobj(o) {
     function f() {};
     f.prototype = o;
     return new f();
    }
     
    function inheritproto(subclass, supclass) {
     subclass.prototype = inheritobj(supclass.prototype);
     subclass.prototype.constructor = subclass;
    }
     
    function extend(p, o) {
     for(var item in o) {
      if(!p.hasownproperty(item)) {
       p[item] = o[item];
      }
     }
    }
     
    function container(classname) {
     this.element = null;
     this.classname = classname || '';
     this.parent = null;
     this.children = [];
     this.init();
    }
    container.prototype.init = function() {
     this.element = document.createelement(this.tagname || 'div');
     this.classname && (this.element.classname = this.classname);
    }
    container.prototype.getelement = function() {
     return this.element;
    }
    container.prototype.add = function(item) {
     item.parent = this;
     this.children.push(item);
     this.element.appendchild(item.getelement());
     return this;
    }
     
    function item(classname, tagname) {
     this.tagname = tagname || 'div';
     container.call(this, classname);
     delete this.children;
    }
    inheritproto(item, container);
    item.prototype.add = function() {
     throw new error('[[type item]] can not add any other item');
    }

    colorpicker.js:

    (function() {
     this.colorpicker = function() {
      container.call(this);
      this.hsv = [0, 0, 0];
      this.rgb = [0, 0, 0];
      this.svfieldhsv = [0, 1, 1];
      this.svfieldrgb = [0, 0, 0];
     }
     inheritproto(colorpicker, container);
     colorpicker.prototype.init = function() {
      this.element = document.createelement('div');
      this.element.classname = 'color-picker-container';
      var _container = createcontainer(),
       _self = this;
      event.call(_container);
      extend(_container, event.prototype);
      createval.call(this);
      createsv.call(_container);
      createh.call(_container);
      _container.addevent('sv-change', function(e) {
       // 暴露出饱和度change接口
       _self.onsvchange(e);
      });
      _container.addevent('h-change', function(e) {
       // 暴露出色相change接口
       _self.onhchange(e);
      });
      this.add(_container);
     }
     colorpicker.prototype.select = function(opt) {
      if(opt && typeof opt !== 'undefined') {
       this.hsv[0] = opt.h || 0;
       this.hsv[1] = opt.s || 0;
       this.hsv[2] = opt.b || 0;
       this.svfieldhsv[0] = opt.hue || 0;
      }
      if(this.children[0].children[0].getelement().value !== '') {
       var val = this.children[0].children[0].getelement().value,
        regrgb = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/,
        r = val.replace(regrgb, '$1'),
        g = val.replace(regrgb, '$2'),
        b = val.replace(regrgb, '$3');
        this.hsv = rgb2hsv(r, g, b);
        this.svfieldhsv[0] = this.hsv[0];
      }
      this.svfieldrgb = hsv2rgb(this.svfieldhsv[0], this.svfieldhsv[1], this.svfieldhsv[2]);
      this.updatesvfield();
      this.rgb = hsv2rgb(this.hsv[0], this.hsv[1], this.hsv[2]);
      this.updatesvpointer();
      this.updateval(opt);
      this.children[1].children[0].getelement().style.csstext += ';left: ' + this.hsv[1] * 255 + 'px;top: ' + (255 - this.hsv[2] * 255) + 'px;';
      this.children[1].children[2].children[0].getelement().style.csstext += ';top: ' + (255 - this.hsv[0]) + 'px;';
      document.body.appendchild(this.element);
      return this;
     }
     colorpicker.prototype.updatesvfield = function() {
      this.children[1].children[1].getelement().style.csstext = ';background: -webkit-linear-gradient(left, rgb(255, 255, 255) 0%, rgb(' + ~~this.svfieldrgb[0] + ', ' + ~~this.svfieldrgb[1] + ', ' + ~~this.svfieldrgb[2] + ') 100%)';
     }
     colorpicker.prototype.updatesvpointer = function() {
      this.children[1].children[0].getelement().style.csstext += ';background-color: rgb(' + ~~this.rgb[0] + ', ' + ~~this.rgb[1] + ', ' + ~~this.rgb[2] + ')';
     }
     colorpicker.prototype.updateval = function() {
      var _hsv_temp = [0, 0, 0];
      if(this.hsv[1] < 0.5 && this.hsv[2] > 0.5) {
       _hsv_temp = [this.hsv[0], 1, 0];
      } else {
       _hsv_temp = [this.hsv[0], 0, 1];
      }
      var _rgb_temp = hsv2rgb(_hsv_temp[0], _hsv_temp[1], _hsv_temp[2]);
      this.children[0].children[0].getelement().style.csstext += ';text-shadow: 0 0 5px;color: rgb(' + ~~_rgb_temp[0] + ', ' + ~~_rgb_temp[1] + ', ' + ~~_rgb_temp[2] + ');background-color: rgb(' + ~~this.rgb[0] + ', ' + ~~this.rgb[1] + ', ' + ~~this.rgb[2] + ')';
      this.children[1].children[0].getelement().style.csstext += ';border-color: rgb(' + ~~_rgb_temp[0] + ', ' + ~~_rgb_temp[1] + ', ' + ~~_rgb_temp[2] + ')';
      this.children[0].children[0].getelement().value = 'rgb(' + ~~this.rgb[0] + ', ' + ~~this.rgb[1] + ', ' + ~~this.rgb[2] + ')';
     }
     colorpicker.prototype.onsvchange = function(callval) {
      arguments.callee.call(this, callval);
      return this;
     }
     colorpicker.prototype.onhchange = function(callval) {
      arguments.callee.call(this, callval);
      return this;
     }
     
     function createval() {
      var _container = new container('val-container'),
       _input = new item('val', 'input');
      event.call(_input);
      extend(_input, event.prototype);
      _input.addevent('blur', this.select.bind(this));
      _container.add(_input);
      this.add(_container);
     }
     
     function createcontainer() {
      var _container = new container('picker-container');
      return _container;
     }
     
     function createsv() {
      var _pointer = new item('pointer'),
       _saturationrange = new container('saturation-range'),
       _cover = new item('cover'),
       _self = this;
      event.call(_pointer);
      extend(_pointer, event.prototype);
      _saturationrange.add(_cover);
     
      function cursordown(e) {
       var _top = typeof e.target.style.top === 'undefined' ? 255 : parsefloat(e.target.style.top),
        _left = typeof e.target.style.left === 'undefined' ? 0 : parsefloat(e.target.style.left),
        _distancey, _distancex, realtop, realleft;
     
       function move(e2) {
        _distancey = e2.clienty - e.clienty;
        _distancex = e2.clientx - e.clientx;
        realtop = _top + _distancey;
        realleft = _left + _distancex;
        realtop < 0 && (realtop = 0);
        realtop > 255 && (realtop = 255);
        realleft < 0 && (realleft = 0);
        realleft > 255 && (realleft = 255);
        e.target.style.top = realtop + 'px';
        e.target.style.left = realleft + 'px';
        _self.parent.hsv[1] = realleft / 255;
        _self.parent.hsv[2] = (255 - realtop) / 255;
        _self.parent.rgb = hsv2rgb(_self.parent.hsv[0], _self.parent.hsv[1], _self.parent.hsv[2]);
        _self.parent.updatesvpointer();
        _self.parent.updateval();
       }
     
       function up() {
        document.removeeventlistener('mousemove', move);
        document.removeeventlistener('mouseup', up);
        _self.triggerevent('sv-change', [realleft / 255, (255 - realtop) / 255]);
       }
       document.addeventlistener('mousemove', move);
       document.addeventlistener('mouseup', up);
      }
      _pointer.addevent('mousedown', cursordown);
      this.add(_pointer)
       .add(_saturationrange);
     }
     
     function createh() {
      var _huerange = new container('hue-range'),
       _cursor = new item('cursor'),
       _self = this;
      event.call(_cursor);
      extend(_cursor, event.prototype);
     
      function cursordown(e) {
       var _top = typeof e.target.style.top === 'undefined' ? 255 : parsefloat(e.target.style.top),
        _distance, realtop;
     
       function move(e2) {
        _distance = e2.clienty - e.clienty;
        realtop = _top + _distance;
        realtop < 0 && (realtop = 0);
        realtop > 255 && (realtop = 255);
        e.target.style.top = realtop + 'px';
        _self.parent.svfieldhsv[0] = 255 - realtop;
        _self.parent.svfieldrgb = hsv2rgb(_self.parent.svfieldhsv[0], _self.parent.svfieldhsv[1], _self.parent.svfieldhsv[2]);
        _self.parent.updatesvfield();
        _self.parent.hsv[0] = 255 - realtop;
        _self.parent.rgb = hsv2rgb(_self.parent.hsv[0], _self.parent.hsv[1], _self.parent.hsv[2]);
        _self.parent.updatesvpointer();
        _self.parent.updateval();
       }
     
       function up() {
        document.removeeventlistener('mousemove', move);
        document.removeeventlistener('mouseup', up);
        _self.triggerevent('h-change', 255 - realtop);
       }
       document.addeventlistener('mousemove', move);
       document.addeventlistener('mouseup', up);
      }
      _cursor.addevent('mousedown', cursordown);
      _huerange.add(_cursor);
      this.add(_huerange);
     }
     
     function hsv2rgb(h, s, v) {
      h = h / 255 * 360;
      var hi = math.floor(h / 60) % 6,
       f = h / 60 - math.floor(h / 60),
       p = v * (1 - s),
       q = v * (1 - f * s),
       t = v * (1 - (1 - f) * s),
       c = [
       [v, t, p],
       [q, v, p],
       [p, v, t],
       [p, q, v],
       [t, p, v],
       [v, p, q]
      ][hi];
      return [c[0] * 255, c[1] * 255, c[2] * 255];
     }
     
     function rgb2hsv(r, g, b) {
      var max = math.max(r, g, b),
       min = math.min(r, g, b),
       h, s, v,
       d = max - min;
      if (max == min) {
       h = 0;
      } else if (max == r) {
       h = 60 * ((g - b) / d);
      } else if (max == g) {
       h = 60 * ((b - r) / d) + 120;
      } else {
       h = 60 * ((r - g) / d) + 240;
      }
      s = max == 0 ? 0 : (1 - min / max);
      v = max;
      if(h < 0) {
       h += 360;
      }
      return [h * 255 / 360, s, v / 255];
     }
    })()
     
    var aa = new colorpicker();
    aa.select(
     // {
     //  h: 120,
     //  s: 1,
     //  b: 1
     // }
    );
    aa.onhchange = function(e) {};
    aa.onsvchange = function(e) {};