UNPKG

lu2

Version:

Simple and flexible UI component library based on native HTML and JavaScript

712 lines (624 loc) 24.1 kB
/** * @Color.js * @author xunxuzhang * @version * Created: 16-06-03 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Color = factory(); } }(this, function (require, exports, module) { if (typeof require == 'function') { require('common/ui/Drop'); } else if (!$().drop) { if (window.console) { console.error('need Drop.js'); } return {}; } /** * 基于HTML原生color颜色选择 * 兼容IE7+ * type=color Hex format */ var CL = 'ui-color', colorPrefix = CL + '-', defaultValue = '#000000'; // 其他变量 var ACTIVE = 'active', BGCOLOR = 'background-color'; /* 一些颜色间的相互转换的公用方法 */ // hsl颜色转换成十六进制颜色 $.hslToHex = function (h, s, l) { var r, g, b; if (s == 0) { r = g = b = l; // achromatic } else { var hue2rgb = function hue2rgb(p, q, t){ if(t < 0) t += 1; if(t > 1) t -= 1; if(t < 1/6) return p + (q - p) * 6 * t; if(t < 1/2) return q; if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)].map(function(rgb) { rgb = rgb.toString(16); if (rgb.length == 1) { return "0" + rgb; } return rgb; }).join(""); }; // 16进制颜色转换成hsl颜色表示 $.hexToHsl = function (hex) { var r = parseInt("0x" + hex.slice(0,2)) / 255, g = parseInt("0x" + hex.slice(2,4)) / 255, b = parseInt("0x" + hex.slice(4,6)) / 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if (max == min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch(max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h, s, l]; }; // rgb/rgba颜色转hex $.rgbToHex = function(rgb) { if (!rgb) { return defaultValue; } var arr = []; if (/^#[0-9A-F]{6}$/i.test(rgb)) { return rgb; } if (/^#[0-9A-F]{3}$/i.test(rgb)) { arr = rgb.split(''); return arr[0] + arr[1] + arr[1] + arr[2] + arr[2] + arr[3] + arr[3]; } // 如果是rgb(a)色值 arr = rgb.match(/^rgb(?:a)?\((\d+),\s*(\d+),\s*(\d+)/i); var hex = function(x) { return ("0" + parseInt(x).toString(16)).slice(-2); } if (arr.length == 4) { return "#" + hex(arr[1]) + hex(arr[2]) + hex(arr[3]); } return defaultValue; }; /* 包装器方法 */ $.fn.color = function(options) { return $(this).each(function() { if (!$(this).data('color')) { $(this).data('color', new Color($(this), options)); } }); }; var Color = function(el, options) { var self = this; // 参数 var defaults = { offsets: { x: 0, y: 0 }, edgeAdjust: false, position: '7-5', onShow: $.noop, onHide: $.noop }; var params = $.extend({}, defaults, options || {}); // el需要是原生的type=color的输入框 if (!el) { return; } if (!el.size) { el = $(el); } var ele = el[0]; // 一些默认的属性值 var id = ele.id; if (!id) { // 创建随机id id = 'C' + (Math.random() + '').split('.')[1]; ele.id = id; } // 只读 el.prop('readonly', true).on('click', function(event) { // 阻止默认的颜色选择出现 event.preventDefault(); }); // 元素构建 // track是替换输入框的色块元素的轨道 var track = $('<label></label>').addClass(colorPrefix + 'track').attr('for', id); // thumb是替换输入框的色块元素的色块区域 var thumb = $('<span></span>').addClass(colorPrefix + 'thumb'); // 前置插入 el.before(track.append(thumb)); // 浮层容器 var container = $('<div></div>').addClass(colorPrefix + 'container'); // 浮层显隐与定位 track.drop(container, { eventType: 'click', offsets: params.offsets, edgeAdjust: params.edgeAdjust, position: params.position, onShow: function() { self.show(); if (params.edgeAdjust == false) { var rect = container[0].getBoundingClientRect(); if (rect.left < 3) { container.css('margin-left', -1 * rect.left + 3); } else if (rect.right - $(window).width() > 3) { container.css('margin-left', $(window).width() - rect.right - 3); } } }, onHide: function() { self.hide(); container.css('margin-left', '0'); } }); self.drop = track.data('drop'); this.el = { input: el, container: container, track: track, thumb: thumb }; this.callback = { show: params.onShow, hide: params.onHide }; // 初始化 this.value(ele.value); return this; }; /** * 输入框的赋值和取值,同时会更改对应的UI * @param value HEX颜色值,例如'#000000'。可缺省,表示取值 */ Color.prototype.value = function(value) { var self = this; // 元素 var input = self.el.input, thumb = self.el.thumb; // 目前的颜色值 var oldvalue = input.val(); // 取值还是赋值 if (typeof value == 'string') { // 使用hex值 value = $.rgbToHex(value); // 赋值 input.val(value); // 按钮上的色块值 thumb.css(BGCOLOR, value); // 面板上的值,各种定位的匹配 self.match(); } else { // 取值 // 如果默认无值,使用颜色作为色值,一般出现在初始化的时候 if (!oldvalue) { oldvalue = defaultValue; // 赋值 input.val(oldvalue); // 按钮上的色块值 thumb.css(BGCOLOR, oldvalue); } return oldvalue; } if (oldvalue && value != oldvalue) { input.trigger('change'); } return this; }; /** * container内HTML的创建 */ Color.prototype.create = function() { var self = this; // 元素 var container = self.el.container; // switch button var convert = '<a href="javascript:" class="'+ colorPrefix +'switch">更多</a>'; // current color var current = '<div class="'+ colorPrefix +'current">\ <i class="'+ colorPrefix +'current-square colorCurrent"></i>\ #<input class="'+ colorPrefix +'current-input">\ </div>'; // body var body = '<div class="'+ colorPrefix +'body">'+ (function() { // basic color picker var html = '<div class="'+ colorPrefix +'basic colorBasicX">'; // color left html = html + '<div class="'+ colorPrefix +'basic-l">'+ (function() { return $.map(["0", "3", "6", "9", "C", "F", "F00", "018001", "0501ff", "FF0", "0FF", "800180"], function(color) { color = color.toUpperCase(); if (color.length == 1) { color = color + color + color + color + color + color; } else if (color.length == 3) { color = color.replace(/\w/g, function(matchs) { return matchs + matchs; }); } return '<a class="'+ colorPrefix +'lump" data-color="'+ color +'" style="'+ BGCOLOR +':#'+ color +'"></a>'; }).join(''); })() +'</div>'; // color main html = html + '<div class="'+ colorPrefix +'basic-r">' + (function() { var arrBasic = ["0", "3", "6", "9", "C", "F"]; htmlR = ''; $.each(arrBasic, function(i, r) { htmlR += '<div class="'+ colorPrefix +'lump-group">'; $.each(arrBasic, function(j, g) { $.each(arrBasic, function(k, b) { var color = r + r + g + g + b + b; htmlR = htmlR + '<a class="'+ colorPrefix +'lump" data-color="'+ color +'" style="'+ BGCOLOR +':#'+ color +'"></a>'; }); }); htmlR += '</div>'; }); return htmlR; })() +'</div>'; return html + '</div>'; })() + (function() { // more color picker var html = '<div class="'+ colorPrefix +'more colorMoreX">'; // color left html = html + '<div class="'+ colorPrefix +'more-l">\ <a class="'+ colorPrefix +'cover-white"></a><div class="'+ colorPrefix +'circle colorCircle"></div>\ <svg>\ <defs>\ <linearGradient x1="0" y1="0" x2="1" y2="0" id="colorGradient">\ <stop offset="0%" stop-color="#ff0000"></stop>\ <stop offset="16.66%" stop-color="#ffff00"></stop>\ <stop offset="33.33%" stop-color="#00ff00"></stop>\ <stop offset="50%" stop-color="#00ffff"></stop>\ <stop offset="66.66%" stop-color="#0000ff"></stop>\ <stop offset="83.33%" stop-color="#ff00ff"></stop>\ <stop offset="100%" stop-color="#ff0000"></stop>\ </linearGradient>\ </defs>\ <rect x="0" y="0" width="180" height="100" fill="url(#colorGradient)"></rect>\ </svg></div><div class="'+ colorPrefix +'more-r">\ <div class="'+ colorPrefix +'more-fill colorFill">\ <a href="javascript:" class="'+ colorPrefix +'more-cover"></a>\ <svg>\ <defs>\ <linearGradient x1="0" y1="0" x2="0" y2="1" id="colorGradient2">\ <stop offset="0%" stop-color="#ffffff"></stop>\ <stop offset="50%" stop-color="rgba(255,255,255,0)"></stop>\ <stop offset="50%" stop-color="rgba(0,0,0,0)"></stop>\ <stop offset="100%" stop-color="#000000"></stop>\ </linearGradient>\ </defs>\ <rect x="0" y="0" width="16" height="100" fill="url(#colorGradient2)"></rect>\ </svg>\ </div>\ <a href="javascript:" class="'+ colorPrefix +'more-arrow colorArrow"></a>\ </div>'; return html + '</div>'; })() +'</div>'; // footer var footer = '<div class="'+ colorPrefix +'footer">\ <a href="javascript:" class="'+ colorPrefix +'btn-cancel">取消</a><a href="javascript:" class="'+ colorPrefix +'btn-ensure">确定</a>\ </div>'; // append container.html(convert + current + body + footer); // 一些元素 $.extend(self.el, { field: container.find('input'), basic: container.find('.colorBasicX'), more: container.find('.colorMoreX'), circle: container.find('.colorCircle'), fill: container.find('.colorFill'), arrow: container.find('.colorArrow'), current: container.find('.colorCurrent') }); // 事件 self.events(); return self; }; /** * container内的一些事件 */ Color.prototype.events = function() { var self = this; // 元素 var input = self.el.input, container = self.el.container; // 更多元素 // 元素 var circle = self.el.circle, fill = self.el.fill, arrow = self.el.arrow; // 面板内部唯一的输入框元素 var elInput = self.el.field; container.delegate('a', 'click', function(event) { // 元素 var ele = this, el = $(ele); // 选择的颜色值 var value = ''; // 当前类名 var cl = ele.className; // 按钮分类别处理 if (/cancel/.test(cl)) { // 1. 取消按钮 self.hide(); } else if (/ensure/.test(cl)) { // 2. 确定按钮 // 赋值 value = elInput.val(); if (value) { self.value('#' + value); } self.hide(); } else if (/lump/.test(cl)) { // 3. 小色块 value = el.attr('data-color'); elInput.val(value); self.match(); } else if (/switch/.test(cl)) { // 4. 面板类名切换按钮 if (ele.innerHTML == "更多") { self.el.more.show(); self.el.basic.hide(); ele.innerHTML = "基本"; } else { self.el.more.hide(); self.el.basic.show(); ele.innerHTML = "更多"; } // 面板的色块啊,圆和尖角位置匹配 self.match(); } else if (/cover/.test(cl)) { // 5. 渐变色的覆盖层 // offsetLeft, offsetTop var rect = ele.getBoundingClientRect(), offsetLeft = event.pageX - rect.left, offsetTop = event.pageY - $(document).scrollTop() - rect.top; // width, height var width = ele.clientWidth, height = ele.clientHeight; // color var colorH , colorS, colorL; if (circle.length && fill.length && arrow.length) { if (/white/.test(cl) == true) { colorH = offsetLeft / width; colorS = 1 - offsetTop / height; colorL = 1 - arrow.css('top').replace("px", "") / arrow.parent().height(); circle.css({ left: offsetLeft, top: offsetTop }); var hsl = "hsl("+ [360 * colorH, 100 * colorS + "%", "50%"].join() +")"; circle.css(BGCOLOR, hsl); //fill.css(BGCOLOR, "hsl("+ [360 * colorH, 100 * colorS + "%", "50%"].join() +")"); } else { arrow.css('top', offsetTop); } // 赋值 elInput.val(self._getHSL().replace('#', '')); self.match(); } } }); // 输入框事件 var field = self.el.field; field.on('input', function() { var value = this.value; if (/^[0-9A-F]{6}$/i.test(value)) { self.match(); } else if (/^[0-9A-F]{3}$/i.test(value)) { self.match($.rgbToHex('#' + value).replace('#', '')); } }).on('keyup', function(event) { if (event.keyCode == 13) { var value = elInput.val(), oldvalue = value; if (value) { value = $.rgbToHex('#' + value); if (value != oldvalue) { // 支持输入#000 elInput.val(value); } self.value('#' + value); } self.hide(); } }); // IE7,IE8 if (![].map) { field[0].attachEvent('onpropertychange', function(event) { event = event || window.event; if (event && event.propertyName == 'value') { field.trigger('input'); } }); } else { // 滑块拖动事件 var oPosArrow = {}; arrow.on('mousedown', function(event) { oPosArrow.pageY = event.pageY; oPosArrow.top = arrow.css('top').replace("px", "") * 1; event.preventDefault(); }); document.addEventListener("mousemove", function(event) { if (typeof oPosArrow.top == "number") { var top = oPosArrow.top + (event.pageY - oPosArrow.pageY), maxTop = arrow.parent().height(); if (top < 0) { top = 0; } else if (top > maxTop) { top = maxTop; } arrow.css('top', top); // 赋值,此次赋值,无需重定位 elInput.val(self._getHSL().replace('#', '')); self.match(false); } }); document.addEventListener("mouseup", function() { oPosArrow.top = null; }); } return self; }; /** * 根据坐标位置获得hsl值 */ Color.prototype._getHSL = function() { var self = this; // 需要的元素 var circle = self.el.circle, arrow = self.el.arrow; if (circle.length * arrow.length == 0) { return self; } var colorH, colorS, colorL; // get color // hsl color if (circle[0].style.left) { colorH = circle.css('left').replace("px", "") / circle.parent().width() } else { colorH = 0; } if (circle[0].style.top) { colorS = 1 - circle.css('top').replace("px", "") / circle.parent().height(); } else { colorS = 1; } if (arrow[0].style.top) { colorL = 1 - arrow.css('top').replace("px", "") / arrow.parent().height(); } else { colorL = 0; } return "#" + $.hslToHex(colorH, colorS, colorL); }; /** * 面板的色块啊,圆和尖角位置匹配 */ Color.prototype.match = function(value) { var self = this; // 首先要面板显示 if (self.display != true) { return this; } // 一些元素 var container = self.el.container, current = self.el.current, circle, fill, arrow, input = self.el.input; var el = self.el.field; // 重定位 var rePosition = true; if (value === false) { rePosition = value; } // 当前的颜色值 var value = value || el.val(); if (value == '') { // 如果输入框没有值 // 使用之前一个合法的颜色值作为现在值 value = $.rgbToHex(current.css(BGCOLOR)).replace('#', ''); el.val(value); } value = value.replace('#', ''); // 色块值示意 current.css(BGCOLOR, '#' + value); // 当前是基本色面板还是任意色面板 if (self.el.more.css('display') == 'none') { // 1. 基本色 // 所有当前高亮的元素不高亮 container.find('.' + ACTIVE).removeClass(ACTIVE); // 所有颜色一致的高亮 container.find("a[data-color='"+ value.toUpperCase() +"']").addClass(ACTIVE); } else { circle = self.el.circle; fill = self.el.fill; arrow = self.el.arrow; // to HSL var arrHSL = $.hexToHsl(value); // hsl value var colorH = arrHSL[0], colorS = arrHSL[1], colorL = arrHSL[2]; // 滑块和尖角的颜色和位置 var hsl = "hsl("+ [360 * colorH, 100 * colorS + "%", "50%"].join() +")"; if (value != '000000') { circle.css(BGCOLOR, hsl); fill.css(BGCOLOR, hsl); } if (rePosition == true) { if (colorL != 0) { circle.css({ left: circle.parent().width() * colorH, top: circle.parent().height() * (1 - colorS) }); } arrow.css('top', arrow.parent().height() * (1 - colorL)); } } return this; }; /** * 面板显示 */ Color.prototype.show = function() { var self = this; // 元素 var container = self.el.container, body = $('body'); // z-index自动最高 var zIndex = container.css('zIndex') * 1 || 21, maxIndex = zIndex; body.children().each(function() { var z; if ($(this).hasClass(CL) == false && (z = $(this).css('zIndex') * 1)) { maxIndex = Math.max(z, maxIndex); } }); if (zIndex < maxIndex) { container.css('zIndex', maxIndex + 1); } // 输入框赋值 if (container.html() == '') { self.create(); } // 面板显示 if (self.drop.display == false) { self.drop.show(); } self.display = self.drop.display; // 面板UI匹配 var current = self.el.current; if (!current.attr('style')) { current.css(BGCOLOR, self.el.input.val()); } self.match(); // onShow callback if ($.isFunction(this.callback.show)) { this.callback.show.call(this, this.el.track, container); } return this; }; Color.prototype.hide = function() { var self = this; // 面板隐藏 if (self.drop.display == true) { self.drop.hide(); } self.display = self.drop.display; // onShow callback if ($.isFunction(this.callback.hide)) { this.callback.hide.call(this, this.el.trigger, this.el.container); } return this; }; return Color; }));