lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
854 lines (739 loc) • 28.5 kB
JavaScript
/**
* @Color.js
* @author zhangxinxu
* @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) {
if (typeof require == 'function') {
require('common/ui/Drop');
} else if (!$().drop) {
if (window.console) {
window.console.error('need Drop.js');
}
return {};
}
/**
* 基于HTML原生color颜色选择
* 兼容IE7+
* type=color Hex format
*/
var CL = 'ui-color';
var colorPrefix = CL + '-';
var defaultValue = '#000000';
// 其他变量
var ACTIVE = 'active';
var BGCOLOR = 'background-color';
/* 一些颜色间的相互转换的公用方法 */
// hsl颜色转换成十六进制颜色
$.hslToHex = function (h, s, l) {
var r, g, b;
if (s == 0) {
// 非彩色的
r = g = b = l;
} 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);
}
var arrRgb = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
return $.map(arrRgb, function (rgb) {
rgb = rgb.toString(16);
if (rgb.length == 1) {
return '0' + rgb;
}
return rgb;
}).join('');
};
// 16进制颜色转换成hsl颜色表示
$.hexToHsl = function (hex) {
var r = parseInt(hex.slice(0, 2), 16) / 255;
var g = parseInt(hex.slice(2, 4), 16) / 255;
var b = parseInt(hex.slice(4, 6), 16) / 255;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s;
var l = (max + min) / 2;
if (max == min) {
// 非彩色
h = s = 0;
} 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(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
var hex = function(x) {
return ('0' + parseInt(x, 10).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 for="' + id + '"></label>').addClass(colorPrefix + 'track');
// thumb是替换输入框的色块元素的色块区域
var thumb = $('<span></span>').addClass(colorPrefix + 'thumb');
// 前置插入
el.before(track.append(thumb));
// 浮层容器
var container = $('<div></div>').addClass(colorPrefix + 'container');
// 浮层显隐与定位
el.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');
}
});
// 键盘可访问性处理
el.on('focus', function () {
if (window.isKeyEvent) {
track.addClass('ui-outline');
}
}).blur(function () {
track.removeClass('ui-outline');
});
self.drop = el.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 {String} value HEX颜色值,例如'#000000'。可缺省,表示取值
* @return {Object} 返回当前的实例对象
*/
Color.prototype.value = function(value) {
var self = this;
// 元素
var input = self.el.input;
var 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的创建
* @return {Object} 返回当前实例对象
*/
Color.prototype.create = function() {
var self = this;
// 元素
var container = self.el.container;
// switch button
var convert = '<a href="javascript:" class="' + colorPrefix + 'switch" role="button">更多</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" role="listbox">';
// 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 href="javascript:" class="' + colorPrefix + 'lump" data-color="' + color + '" aria-label="' + color + '" style="' + BGCOLOR + ':#' + color + '" role="option"></a>';
}).join('');
})() + '</div>';
// color main
html = html + '<div class="' + colorPrefix + 'basic-r">' + (function() {
var arrBasic = ['0', '3', '6', '9', 'C', 'F'];
var 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 href="javascript:" class="' + colorPrefix + 'lump" data-color="' + color + '" style="' + BGCOLOR + ':#' + color + '" aria-label="' + color + '" role="option"></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 href="javascript:" class="' + colorPrefix + 'cover-white" aria-label="色域背景块" role="region"></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" aria-label="明度控制背景条" role="region"></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" role="slider" aria-label="明度控制按钮:100%"></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内的一些事件
* @return {Object} 返回当前实例对象
*/
Color.prototype.events = function() {
var self = this;
// 元素
var container = self.el.container;
// 更多元素
// 元素
var circle = self.el.circle;
var fill = self.el.fill;
var arrow = self.el.arrow;
// 面板内部唯一的输入框元素
var elInput = self.el.field;
// var keycode = {
// 37: 'left',
// 38: 'up',
// 39: 'right',
// 40: 'down',
// 13: 'enter'
// };
container.on('click', 'a', function(event) {
// 元素
var ele = this;
var 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();
var offsetLeft = event.pageX - rect.left;
var offsetTop = event.pageY - $(document).scrollTop() - rect.top;
// width, height
var width = ele.clientWidth;
var 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('#', ''));
// UI变化
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();
var 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);
var 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;
});
// 滑块的键盘支持
fill.parent().find('a').on('keydown', function (event) {
// 上下控制
if (event.keyCode == 38 || event.keyCode == 40) {
event.preventDefault();
var top = arrow.css('top').replace('px', '') * 1;
var maxTop = fill.height();
if (event.keyCode == 38) {
top--;
if (top < 0) {
top = 0;
}
} else {
top++;
if (top > maxTop) {
top = maxTop;
}
}
var ariaLabel = arrow.attr('aria-label');
arrow.css('top', top).attr('aria-label', ariaLabel.replace(/\d+/, Math.round(100 * top / maxTop)));
// 赋值,此次赋值,无需重定位
elInput.val(self._getHSL().replace('#', ''));
self.match(false);
}
});
// 区域背景的键盘支持
circle.parent().find('a').on('keydown', function (event) {
var region = $(this);
// 上下控制
if (event.keyCode >= 37 && event.keyCode <= 40) {
event.preventDefault();
var top = circle.css('top').replace('px', '') * 1;
var left = circle.css('left').replace('px', '') * 1;
var maxTop = region.height();
var maxLeft = region.width();
if (event.keyCode == 38) {
// up
top--;
if (top < 0) {
top = 0;
}
} else if (event.keyCode == 40) {
// down
top++;
if (top > maxTop) {
top = maxTop;
}
} else if (event.keyCode == 37) {
// left
left--;
if (left < 0) {
left = 0;
}
} else if (event.keyCode == 39) {
// down
left++;
if (left > maxLeft) {
left = maxLeft;
}
}
var colorH = left / maxLeft;
var colorS = 1 - top / maxTop;
//var colorL = 1 - arrow.css('top').replace('px', '') / fill.height();
circle.css({
left: left,
top: top
});
var hsl = 'hsl(' + [360 * colorH, 100 * colorS + '%', '50%'].join() + ')';
circle.css(BGCOLOR, hsl);
// 赋值
elInput.val(self._getHSL().replace('#', ''));
self.match();
}
});
}
return self;
};
/**
* 根据坐标位置获得hsl值
* @return {String} [返回当前坐标对应的hex表示的颜色值]
*/
Color.prototype._getHSL = function() {
var self = this;
// 需要的元素
var circle = self.el.circle;
var 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);
};
/**
* 面板的色块啊,圆和尖角位置匹配
* @param {String} value 面板UI相匹配的色值,可缺省,表示使用当前输入框的颜色值进行UI变化
* @return {Object} 返回当前实例对象
*/
Color.prototype.match = function(value) {
var self = this;
// 首先要面板显示
if (self.display != true) {
return this;
}
// 一些元素
var container = self.el.container;
var current = self.el.current;
// 更多元素
var circle, fill, arrow;
// 主元素
var el = self.el.field;
// 重定位
var rePosition = true;
if (value === false) {
rePosition = value;
}
// 当前的颜色值
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];
var colorS = arrHSL[1];
var 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;
};
/**
* 颜色面板显示
* @return {Object} 返回当前实例对象
*/
Color.prototype.show = function() {
var self = this;
// 元素
var container = self.el.container;
// 输入框赋值
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;
};
/**
* 颜色面板隐藏
* @return {Object} 返回当前实例对象
*/
Color.prototype.hide = function() {
var self = this;
// 面板隐藏
if (self.drop.display == true) {
self.drop.hide();
}
self.display = self.drop.display;
self.el.input.focus();
// onShow callback
if ($.isFunction(this.callback.hide)) {
this.callback.hide.call(this, this.el.trigger, this.el.container);
}
return this;
};
return Color;
}));