js实现拾色器插件(ColorPicker)

 更新时间:2020年5月21日 16:23  点击:2247

对一个前端来说,颜色选择的插件肯定不陌生,许多小伙伴对这类插件的实现可能会比较好奇。这里奉上原生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) {};

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • JavaScript判断浏览器及其版本信息

    本篇文章主要分享了通过window.navigator来判断浏览器及其版本信息的实例代码。具有一定的参考价值,下面跟着小编一起来看下吧...2017-01-23
  • js实现浏览器打印功能的示例代码

    这篇文章主要介绍了js如何实现浏览器打印功能,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-15
  • Nest.js参数校验和自定义返回数据格式详解

    这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
  • 利用JS实现点击按钮后图片自动切换的简单方法

    下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
  • 详解前端安全之JavaScript防http劫持与XSS

    作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
  • js实现调用网络摄像头及常见错误处理

    这篇文章主要介绍了js实现调用网络摄像头及常见错误处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-07
  • JS实现随机生成验证码

    这篇文章主要为大家详细介绍了JS实现随机生成验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-06
  • js组件SlotMachine实现图片切换效果制作抽奖系统

    这篇文章主要介绍了js组件SlotMachine实现图片切换效果制作抽奖系统的相关资料,需要的朋友可以参考下...2016-04-19
  • 基于JavaScript实现文字超出部分隐藏

    这篇文章主要介绍了基于JavaScript实现文字超出部分隐藏 的相关资料,需要的朋友可以参考下...2016-03-01
  • js实现列表按字母排序

    这篇文章主要为大家详细介绍了js实现列表按字母排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-11
  • JS实现响应鼠标点击动画渐变弹出层效果代码

    这篇文章主要介绍了JS实现响应鼠标点击动画渐变弹出层效果代码,具有非常自然流畅的动画过度效果,涉及JavaScript针对鼠标事件的响应及页面元素样式的动态操作相关技巧,需要的朋友可以参考下...2016-03-28
  • NodeJS实现阿里大鱼短信通知发送

    本文给大家介绍的是nodejs实现使用阿里大鱼短信API发送消息的方法和代码,有需要的小伙伴可以参考下。...2016-01-20
  • vue.js 表格分页ajax 异步加载数据

    Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统.这篇文章主要介绍了vue.js 表格分页ajax 异步加载数据的相关资料,需要的朋友可以参考下...2016-10-20
  • node.JS md5加密中文与php结果不一致怎么办

    这次文章要给大家介绍的是node.JS md5加密中文与php结果不一致怎么办,不知道具体解决办法的下面跟小编一起来看看。 因项目需要,需要Node.js与PHP做接口调用,发现nod...2017-07-06
  • js实现上传图片及时预览

    这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
  • 浅析AngularJS Filter用法

    系统的学习了一下angularjs,发现angularjs的有些思想根php的模块smarty很像,例如数据绑定,filter。如果对smarty比较熟悉的话,学习angularjs会比较容易一点,这篇文章给大家介绍angularjs filter用法详解,感兴趣的朋友一起学习吧...2015-12-29
  • 基于JavaScript实现表单密码的隐藏和显示出来

    为了网站的安全性,很多朋友都把密码设的比较复杂,但是如何密码不能明显示,不知道输的是对是错,为了安全起见可以把密码显示的,那么基于js代码如何实现的呢?下面通过本文给大家介绍JavaScript实现表单密码的隐藏和显示,需要的朋友参考下...2016-03-03
  • 一个关于JS正则匹配的踩坑记录

    这篇文章主要给大家介绍了一个关于JS正则匹配的踩坑记录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-13
  • Vue.js中轻松解决v-for执行出错的三个方案

    v-for标签可以用来遍历数组,将数组的每一个值绑定到相应的视图元素中去,下面这篇文章主要给大家介绍了关于在Vue.js中轻松解决v-for执行出错的三个方案,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。...2017-06-15
  • NODE.JS加密模块CRYPTO常用方法介绍

    使用require('crypto')调用加密模块。加密模块需要底层系统提供OpenSSL的支持。它提供了一种安全凭证的封装方式,可以用于HTTPS安全网络以及普通HTTP连接。该模块还提供了一套针对OpenSSL的hash(哈希),hmac(密钥哈希),cipher...2014-06-07