Файл: jcolor.js
Строк: 742
<?php
(function ($) {
'use strict';
function clamp(val, min, max) { return Math.max(Math.min(val, max), min); }
function identity(val) { return val.toFixed(2); }
function uchar(val) { return ((val * 255) | 0) + ''; }
function degree(val) { return ((val * 360) | 0) + ''; }
function percent(val) { return (val * 100).toFixed(2) + '%'; }
var base64encode = window.btoa || function (str) {
var result = [],
position = -1,
len = str.length,
nan0, nan1, nan2, enc = [ ,,, ];
while (++position < len) {
nan0 = str.charCodeAt(position);
nan1 = str.charCodeAt(++position);
enc[0] = nan0 >> 2;
enc[1] = ((nan0 & 3) << 4) | (nan1 >> 4);
if (isNaN(nan1)) {
enc[2] = enc[3] = 64;
} else {
nan2 = str.charCodeAt(++position);
enc[2] = ((nan1 & 15) << 2) | (nan2 >> 6);
enc[3] = (isNaN(nan2)) ? 64 : nan2 & 63;
}
result.push(
alphabet.charAt(enc[0]),
alphabet.charAt(enc[1]),
alphabet.charAt(enc[2]),
alphabet.charAt(enc[3]));
}
return result.join('');
};
var componentMatchers = { r: /R/g, g: /G/g, b: /B/g, h: /H/g, s: /S/g, l: /L/g, a: /A/g, };
var componentMap = { r: uchar, g: uchar, b: uchar, h: degree, s: percent, l: percent, a: identity, };
var isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
var isUnsupportedBrowser = /MSIE [1-8]/.test(navigator.userAgent);
var supportsGradients = !(/MSIE 9.0/.test(navigator.userAgent));
var supportsTransitions = supportsGradients;
var isTouchDevice = 'ontouchstart' in document.documentElement || 'ontouchstart' in window;
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var staticComponentValues = { r: 0, g: 0, b: 0, h: 0, s: 1, l: 0.5, a: 1 };
var componentStops = { r: 2, g: 2, b: 2, h: 7, s: 2, l: 3, a: 2 };
var LEFT_BUTTON = 0;
var TRANSITION_END = isSafari ? 'webkitTransitionEnd' : 'transitionend';
var POINTER_DOWN_EVENT = isTouchDevice ? 'touchstart' : 'mousedown';
var POINTER_MOVE_EVENT = isTouchDevice ? 'touchmove' : 'mousemove';
var POINTER_UP_EVENT = isTouchDevice ? 'touchend touchcancel' : 'mouseup';
var NEW_COLOR_EVENT = 'newcolor';
var EXPANDED_Z_INDEX = 999999;
function getGradientTemplate(component, space, staticComponents) {
var colorComponents = staticComponents ? space.split('').map(function (component) {
return componentMap[component](staticComponentValues[component]);
}) : space.split('');
var componentIndex = space.indexOf(component);
var colors = [];
var n = componentStops[component];
var i;
// Generate the color stops, e.g. "rgba(R, G, 0, A),rgba(R, G, 255, A)"
for (i = 0; i < n; ++i) {
var frac = i / (n - 1);
colorComponents[componentIndex] = componentMap[component](frac);
var color = supportsGradients ?
space + '(' + colorComponents.join().toUpperCase() + ')' :
'<stop stop-color="' + space + '(' + colorComponents.join().toUpperCase() + ')" offset="' + frac + '"/>';
colors.push(color);
}
colors = colors.join(supportsGradients ? ',' : '');
// The template function substitutes the color placeholders, e.g. R, G, A for the
// color values specified in the color argument
return function (color) {
var ret = colors;
for (var c in color) {
c === component || (ret = ret.replace(componentMatchers[c], componentMap[c](color[c])));
}
if (supportsGradients) {
return 'linear-gradient(to right, ' + ret + ')';
} else {
var svg =
'<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" ' +
'viewBox="0 0 1 1" preserveAspectRatio="none">' +
'<linearGradient id="the-gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="100%" y2="0%">' +
ret +
'</linearGradient>' +
'<rect x="0" y="0" width="1" height="1" fill="url(#the-gradient)" />' +
'</svg>';
return 'url(data:image/svg+xml;base64,' + base64encode(svg) + ')';
}
};
}
function getRgb(c) {
if ('r' in c) { return c; }
var r, g, b;
var h = c.h, s = c.s, l = c.l;
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;
}
if (s === 0) {
r = g = b = l; // achromatic
} else {
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 ret = { r: r, g: g, b: b };
'a' in c && (ret.a = c.a);
return ret;
}
function getHsl(c) {
if ('h' in c) { return c; }
var r = c.r, g = c.g, b = c.b;
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;
}
var ret = { h: h, s: s, l: l };
'a' in c && (ret.a = c.a);
return ret;
}
// COLOR CLASS
function Color(space, color) {
var i, components = {};
// Validate color space argument
space = space.toLowerCase();
if (!(/^(rgb|hsl)a?$/i).test(space)) {
throw 'Color spaces must be any of the following: "rgb", "rgba", "hsl" or "hsla"';
}
// Validate color argument
if (typeof color === 'string') {
color = color.toLowerCase();
if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/.test(color)) {
space.length === 4 && color.length !== 9 && (color += 'ff'); // Default alpha to fully opaque
for (i = 1; i < color.length; i += 2) {
components[space[(i - 1) / 2]] = parseInt(color.substr(i, 2), 16) / 255;
}
} else if (/^(rgb|hsl)a?([ds,.%]+)$/.test(color)) {
var parsedSpace = color.match(/(rgb|hsl)a?/)[0];
var componentsArray = color.match(/[d.]+/g);
if (/rgb/.test(parsedSpace)) {
components.r = componentsArray[0] / 255;
components.g = componentsArray[1] / 255;
components.b = componentsArray[2] / 255;
} else {
components.h = componentsArray[0] / 360;
components.s = componentsArray[1] / 100;
components.l = componentsArray[2] / 100;
}
components.a = +componentsArray[3];
} else {
throw 'Color strings must be hexadecimal (e.g. "#00ff00", or "#00ff00ff") or CSS style (e.g. rgba(0,255,0,1))';
}
} else if ($.isPlainObject(color)) {
var sortedColorComponentKeys = Object.keys(color).sort().join('');
if (!(/^a?(bgr|hls)$/i).test(sortedColorComponentKeys)) {
throw 'Color objects must contain either r, g and b keys, or h, s and l keys. The a key is optional.';
}
if (space.split('').sort().join('') !== sortedColorComponentKeys) {
color = /rgb/.test('space') ? getRgb(color) : getHsl(color);
}
components = color;
} else {
throw 'Unrecognized color format';
}
// Make sure the color matches the color space. No-op if they are already the same
components = /rgb/.test(space) ? getRgb(components) : getHsl(components);
// Add an alpha channel if missing
isFinite(components.a) || (components.a = 1);
for (var key in components) {
if (components[key] < 0 || 1 < components[key]) { throw 'Color component out of range: ' + key; }
}
this.getComponents = function () { return components; };
this.getComponent = function (componentKey) { return components[componentKey]; };
this.setComponent = function (componentKey, value) { components[componentKey] = value; };
this.getSpace = function () { return space; };
}
Color.prototype.convertComponents = function (newSpace) {
newSpace = newSpace || this.getSpace();
var components = $.extend({}, this.getComponents());
// See if the components are already in the right space
if (new RegExp(newSpace).test(this.getSpace())) {
newSpace.length === 3 && (delete components.a);
return components;
}
components = /rgb/.test(newSpace) ? getRgb(components) : getHsl(components);
if (/a/.test(newSpace)) {
var a = this.getComponent('a');
components.a = typeof a === 'undefined' ? 1 : a;
}
return components;
};
Color.prototype.componentsToString = function (components, space) {
function pad(str) {
return (str.length === 1 ? '0' : '') + str;
}
var str = '#';
for (var i = 0; i < space.length; ++i) {
str += pad(Math.floor(255 * components[space[i]]).toString(16));
}
return str;
};
Color.prototype.toString = function (space) {
space = space || this.getSpace();
return this.componentsToString(this.convertComponents(space), space);
};
Color.prototype.componentsToCssValuesString = function (components, space) {
var componentArray = [];
for (var i = 0; i < space.length; ++i) {
var componentKey = space[i];
componentArray.push(componentMap[componentKey](components[componentKey]));
}
return componentArray.join(', ');
};
Color.prototype.toCssString = function (space) {
space = space || this.getSpace();
return space + '(' + this.componentsToCssValuesString(this.convertComponents(space), space) + ')';
};
// COLORPICKER CLASS
function Colorpicker($el, userOptions) {
var self = this;
var originalClasses = $el.attr('class');
var $originalChildren = $el.children().detach();
var originalText = $el.text();
$el.addClass('colorpicker').empty();
var $body = $('body');
var options = $.extend({
color: $el.css('color'),
colorSpace: 'hsla',
expandEvent: 'mousedown touchstart',
collapseEvent: '',
staticComponents: false,
boundingElement: $body,
modal: false,
}, userOptions);
var color = new Color(options.colorSpace, options.color);
$el.addClass('componentcount-' + color.getSpace().length).toggleClass('show-labels', !!options.labels);
function addChild(className, $parent) { return $('<div>').addClass(className).appendTo($parent); }
var $maximizeWrapper = addChild('maximize-wrapper', $el);
var $innerMaximizeWrapper = addChild('inner-maximize-wrapper', $maximizeWrapper);
var $uiWrapper = addChild('ui-wrapper', $innerMaximizeWrapper);
var $displayWrapper = addChild('display-wrapper', $uiWrapper);
var $display = addChild('display', $displayWrapper);
var $sliderContainer = addChild('slider-container', $uiWrapper);
var sliders = $.map(color.getSpace().split(''), function (componentKey) {
var $slider = addChild('slider ' + componentKey, $sliderContainer);
addChild('handle', $slider).attr('data-component', componentKey);
$slider.data({
componentKey: componentKey,
template: getGradientTemplate(componentKey, color.getSpace(), options.staticComponents),
});
return $slider;
});
var $outputWrapper, colorOutputFunction;
if (options.displayColor) {
if (!(/^(hex|css)$/.test(options.displayColor))) { throw 'Invalid displayColor value, should be "hex" or "css"'; }
$outputWrapper = addChild('output-wrapper', $innerMaximizeWrapper);
colorOutputFunction = {
hex: color.toString,
css: color.toCssString,
}[options.displayColor].bind(color, options.displayColorSpace || options.colorSpace);
}
// Listen to new colors, and update the UI accordingly
function onNewColor(ev, self, componentKey, value) {
componentKey && color.setComponent(componentKey, value);
for (var i = 0; i < sliders.length; ++i) {
var $slider = sliders[i];
$slider
.css({
backgroundImage: $slider.data('template')(color.getComponents()),
})
.find('.handle').css({
left: (color.getComponent($slider.data('componentKey')) * 100) + '%',
});
$display.css({ backgroundColor: color.toCssString() });
options.displayColor && $outputWrapper.text(colorOutputFunction());
}
}
$el.on(NEW_COLOR_EVENT, onNewColor);
setTimeout(onNewColor.bind(null, undefined, this), 0);
// Expanding the colorpicker
var onClickOutside = function (ev) {
$(ev.target).closest($el).length || collapse();
};
var expand, collapse;
expand = function () {
$el.addClass('expanded').css({ zIndex: EXPANDED_Z_INDEX });
options.modal && $body.addClass('colorpicker-modal');
var height = 0;
$innerMaximizeWrapper.children().each(function () { height += $(this).outerHeight(true); });
$innerMaximizeWrapper.css({ width: $uiWrapper.width(), height: height });
$(window).on(POINTER_DOWN_EVENT, onClickOutside);
options.collapseEvent && $el.on(options.collapseEvent, collapse);
$el.off(options.expandEvent, expand);
var boundingRect = options.boundingElement[0].getBoundingClientRect();
var uiWrapperRect = $uiWrapper[0].getBoundingClientRect();
var dX = Math.min(0, boundingRect.right - uiWrapperRect.right - 10);
var dY = Math.min(0, boundingRect.bottom - uiWrapperRect.bottom - 10);
$el.css('transform', 'translate(' + dX + 'px, ' + dY + 'px)');
};
collapse = function () {
supportsTransitions ?
$el.off(TRANSITION_END).one(TRANSITION_END, function () { $el.css({ zIndex: '' }); }) :
$el.css({ zIndex: '' });
$el.css({ zIndex: EXPANDED_Z_INDEX - 1 });
$innerMaximizeWrapper.css({ width: '', height: '' });
$el.removeClass('expanded');
options.modal && $body.removeClass('colorpicker-modal');
$(window).off(POINTER_DOWN_EVENT, onClickOutside);
options.expandEvent && $el.on(options.expandEvent, expand);
$el.css('transform', '');
};
options.expandEvent && $el.on(options.expandEvent, expand);
var triggerNewColor = function (ev) {
var $target = $(ev.target);
if (!$target.hasClass('slider')) { return; }
var offsetX = isTouchDevice ?
ev.originalEvent.touches[0].clientX - $(ev.target).offset().left :
$.isNumeric(ev.offsetX) ? ev.offsetX : ev.clientX - $(ev.target).offset().left;
$target.trigger(NEW_COLOR_EVENT, [
self, $target.data('componentKey'), clamp(offsetX / $target.outerWidth(), 0, 1)
]);
ev.preventDefault();
ev.stopPropagation();
};
var onPointerUp = function () {
$(window).off(POINTER_MOVE_EVENT, triggerNewColor);
};
$el.on(POINTER_DOWN_EVENT, function (ev) {
var $target = $(ev.target);
if (ev.type === 'mousedown' && ev.button !== LEFT_BUTTON || !$target.hasClass('slider')) { return; }
triggerNewColor(ev);
$(window).on(POINTER_MOVE_EVENT, triggerNewColor);
$(window).one(POINTER_UP_EVENT, onPointerUp);
});
// Public functions
this.destroy = function () {
$(window).off(POINTER_UP_EVENT, onPointerUp);
$(window).off(POINTER_MOVE_EVENT, triggerNewColor);
$(window).off(POINTER_DOWN_EVENT, onClickOutside);
$el.off().empty().removeData('colorpicker')
.attr('class', originalClasses).html($originalChildren).text(originalText);
};
this.toString = function () { return color.toString.apply(color, arguments); };
this.toCssString = function () { return color.toCssString.apply(color, arguments); };
this.toObject = function () { return color.convertComponents.apply(color, arguments); };
this.getColorSpace = function () { return color.getSpace(); };
this.on = $el.on.bind($el);
this.off = $el.off.bind($el);
$el.data('colorpicker', this);
return this;
}
// REGISTER PLUGIN
$.fn.colorpicker = function (options) {
if (isUnsupportedBrowser) { throw 'Colorpicker does not work with your browser'; }
var $el = this.eq(0);
if (!$el.length) { throw 'No element matched'; }
return $el.data('colorpicker') || new Colorpicker($el, options);
};
}(window.$));
?>