原生js实现的拾色器插件 - ColorPicker

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

 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值