js实现拾色器插件(ColorPicker)


Posted in Javascript onMay 21, 2020

对一个前端来说,颜色选择的插件肯定不陌生,许多小伙伴对这类插件的实现可能会比较好奇,这里奉上原生js版本的拾色器。

效果图:

js实现拾色器插件(ColorPicker)

讲下实现方式:

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) {};

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用倒置滤镜把div倒置,再把table倒置。
Jul 31 Javascript
用js生产批量批处理执行命令
Jul 28 Javascript
用Javascript 和 CSS 实现脚注(Footnote)效果
Sep 09 Javascript
jquery获取radio值(单选组radio)
Oct 16 Javascript
PHP+jQuery实现随意拖动层并即时保存拖动位置
Apr 30 Javascript
JavaScript数据结构之数组的表示方法示例
Apr 12 Javascript
Vuejs实现带样式的单文件组件新方法
May 02 Javascript
Vue.js列表渲染绑定jQuery插件的正确姿势
Jun 29 jQuery
深入理解Vue 的条件渲染和列表渲染
Sep 01 Javascript
jQuery实现表单动态加减、ajax表单提交功能
Jun 08 jQuery
使用preload预加载页面资源时注意事项
Feb 03 Javascript
详细聊聊vue中组件的props属性
Nov 02 Vue.js
原生js实现日期选择插件
May 21 #Javascript
vue+Element中table表格实现可编辑(select下拉框)
May 21 #Javascript
浅谈React中组件逻辑复用的那些事儿
May 21 #Javascript
记一次用ts+vuecli4重构项目的实现
May 21 #Javascript
JS实现图片幻灯片效果代码实例
May 21 #Javascript
Javascript实现秒表计时游戏
May 27 #Javascript
JavaScript实现猜数字游戏
May 20 #Javascript
You might like
通过对php一些服务器端特性的配置加强php的安全
2006/10/09 PHP
php遍历目录输出目录及其下的所有文件示例
2014/01/27 PHP
如何解决PHP无法实现多线程的问题
2015/09/25 PHP
php获取ajax的headers方法与内容实例
2017/12/27 PHP
php写入mysql中文乱码的实例解决方法
2019/09/17 PHP
php中文语义分析实现方法示例
2019/09/28 PHP
关于laravel-admin ueditor 集成并解决刷新的问题
2019/10/21 PHP
PHP 实现 WebSocket 协议原理与应用详解
2020/04/22 PHP
利用javascript的面向对象的特性实现限制试用期
2011/08/04 Javascript
JS将数字转换成三位逗号分隔的样式(示例代码)
2014/02/19 Javascript
JavaScript获取某年某月的最后一天附截图
2014/06/23 Javascript
深入了解Node.js中的一些特性
2014/09/25 Javascript
JavaScript实现Flash炫光波动特效
2015/05/14 Javascript
举例说明JavaScript中的实例对象与原型对象
2016/03/11 Javascript
bootstrap快速制作后台界面
2016/12/05 Javascript
详解JavaScript中js对象与JSON格式字符串的相互转换
2017/02/14 Javascript
AngularJS页面带参跳转及参数解析操作示例
2017/06/28 Javascript
JS算法题之查找数字在数组中的索引位置
2019/05/15 Javascript
Vue 实现CLI 3.0 + momentjs + lodash打包时优化
2019/11/13 Javascript
VUEX-action可以修改state吗
2019/11/19 Javascript
Vue中keep-alive的两种应用方式
2020/07/15 Javascript
python 删除列表里所有空格项的方法总结
2018/04/18 Python
利用Python如何批量更新服务器文件
2018/07/29 Python
使用 Python 玩转 GitHub 的贡献板(推荐)
2019/04/04 Python
PyCharm取消波浪线、下划线和中划线的实现
2020/03/03 Python
CSS3实现渐变背景兼容问题
2020/05/06 HTML / CSS
什么是方法的重载
2013/06/24 面试题
公务员年总结的自我评价
2013/10/25 职场文书
八项规定整改措施
2014/02/12 职场文书
国际语言毕业生求职信
2014/07/08 职场文书
2014年生产管理工作总结
2014/12/23 职场文书
英文导游词
2015/02/13 职场文书
卡特教练观后感
2015/06/08 职场文书
活动简报范文
2015/07/22 职场文书
关于Mybatis中SQL节点的深入解析
2022/03/19 Java/Android
mysql拆分字符串作为查询条件的示例代码
2022/07/07 MySQL