jquery-wheelcolorpicker
Version:
The Wheel Color Picker plugin adds color picker functionality to HTML form inputs in round color wheel style. The Wheel Color Picker can be displayed as a popup dialog as users focus the input, or embedded inline.
1,570 lines (1,392 loc) • 80.1 kB
JavaScript
/**
* Wheel Color Picker for jQuery
*
* https://raffer.one/projects/jquery-wheelcolorpicker
*
* Author : Fajar Chandra
* Date : 2019.11.05
*
* Copyright © 2011-2019 Fajar Chandra. All rights reserved.
* Released under MIT License.
* http://www.opensource.org/licenses/mit-license.php
*/
(function ($) {
/**
* Function: wheelColorPicker
*
* The wheelColorPicker plugin wrapper. Firing all functions and
* setting/getting all options in this plugin should be called via
* this function.
*
* Before that, if wheelColorPicker instance is not yet initialized,
* this will initialize ColorPicker widget.
*
* This function will look for the options parameter passed in, and
* try to do something as specified in this order:
* 1. If no argument is passed, then initialize the plugin or do nothing
* 2. If object is passed, then call setOptions()
* 3. If string is passed, then try to fire a method with that name
* 4. If string is passed and no method matches the name, then try
* to set/get an option with that name. If a setter/getter method
* available (i.e. setSomething), it will set/get that option via that method.
*/
$.fn.wheelColorPicker = function() {
var returnValue = this; // Allows method chaining
// Separate first argument and the rest..
// First argument is used to determine function/option name
// e.g. wheelColorPicker('setColor', { r: 0, g: 0, b: 1 })
if(arguments.length > 0) {
var shift = [].shift;
var firstArg = shift.apply(arguments);
var firstArgUc = (typeof firstArg === "string") ? firstArg.charAt(0).toUpperCase() + firstArg.slice(1) : firstArg;
}
else {
var firstArg = undefined;
var firstArgUc = undefined;
}
var args = arguments;
this.each(function() {
// Grab ColorPicker object instance
var instance = $(this).data('jQWCP.instance');
// Initialize if not yet created
if(instance == undefined || instance == null) {
// Get init options
var options = {};
if(typeof firstArg === "object") {
options = firstArg;
}
instance = new WCP.ColorPicker(this, options);
$(this).data('jQWCP.instance', instance);
}
/// What to do? ///
// No arguments provided, do nothing
// wheelColorPicker()
if(firstArg === undefined || typeof firstArg === "object") {
}
// Call a method
// wheelColorPicker('show')
else if(typeof instance[firstArg] === "function") {
//console.log('method');
var ret = instance[firstArg].apply(instance, args);
// If instance is not returned, no method chaining
if(ret !== instance) {
returnValue = ret;
return false;
}
}
// Try option setter
// wheelColorPicker('color', '#ff00aa')
else if(typeof instance['set'+firstArgUc] === "function" && args.length > 0) {
//console.log('setter');
var ret = instance['set'+firstArgUc].apply(instance, args);
// If instance is not returned, no method chaining
if(ret !== instance) {
returnValue = ret;
return false;
}
}
// Try option getter
// wheelColorPicker('color')
else if(typeof instance['get'+firstArgUc] === "function") {
//console.log('getter');
var ret = instance['get'+firstArgUc].apply(instance, args);
// If instance is not returned, no method chaining
if(ret !== instance) {
returnValue = ret;
return false;
}
}
// Set option value
// wheelColorPicker('format', 'hex')
else if(instance.options[firstArg] !== undefined && args.length > 0) {
//console.log('set option');
instance.options[firstArg] = args[0];
}
// Get option value
// wheelColorPicker('format')
else if(instance.options[firstArg] !== undefined) {
//console.log('get option');
returnValue = instance.options[firstArg];
return false;
}
// Nothing matches, throw error
else {
$.error( 'Method/option named ' + firstArg + ' does not exist on jQuery.wheelColorPicker' );
}
});
return returnValue;
};
/******************************************************************/
/////////////////////////////////////////
// Shorthand for $.fn.wheelColorPicker //
/////////////////////////////////////////
var WCP = $.fn.wheelColorPicker;
/////////////////////////////////////////
/**
* Object: defaults
*
* Contains default options for the wheelColorPicker plugin.
*
* Member properties:
*
* format - <string> Color naming style. See colorToRgb for all
* available formats.
* live - <boolean> Enable dynamic slider gradients.
* preview - <boolean> Enable live color preview on input field
* userinput - (Deprecated) <boolean> Enable picking color by typing directly
* validate - <boolean> When userinput is enabled, force the value to be
* a valid format. If user input an invalid color, the value
* will be reverted to last valid color.
* autoResize - <boolean> Automatically resize container width.
* If set to false, you could manually adjust size with CSS.
* autoFormat - <boolean> Automatically convert input value to
* specified format. For example, if format is "rgb",
* "#ff0000" will automatically converted into "rgb(255,0,0)".
* color - <object|string> Initial value in any of supported color
* value format or as an object. Setting this value will
* override the current input value.
* alpha - (Deprecated) <boolean> Force the color picker to use alpha value
* despite its selected color format. This option is
* deprecated. Use sliders = "a" instead.
* inverseLabel - (deprecated) Boolean use inverse color for
* input label instead of black/white color.
* preserveWheel - Boolean preserve color wheel shade when slider
* position changes. If set to true, changing
* color wheel from black will reset selectedColor.val
* (shade) to 1.
* interactive - Boolean enable interactive sliders where slider bar
* gradients change dynamically as user drag a slider
* handle. Set to false if this affect performance.
* See also 'quality' option if you wish to keep
* interactive slider but with reduced quality.
* cssClass - Object CSS Classes to be added to the color picker.
* layout - String [block|popup] Layout mode.
* animDuration - Number Duration for transitions such as fade-in
* and fade-out.
* quality - Rendering details quality. The normal quality is 1.
* Setting less than 0.1 may make the sliders ugly,
* while setting the value too high might affect performance.
* sliders - String combination of sliders. If null then the color
* picker will show default values, which is "wvp" for
* normal color or "wvap" for color with alpha value.
* Possible combinations are "whsvrgbap".
* Order of letters will affect slider positions.
* sliderLabel - Boolean Show labels for each slider.
* sliderValue - Boolean Show numeric value of each slider.
* hideKeyboard - Boolean Keep input blurred to avoid on screen keyboard appearing.
* If this is set to true, avoid assigning handler to blur event.
* rounding - Round the alpha value to N decimal digits. Default is 2.
* Set -1 to disable rounding.
* mobile - Display mobile-friendly layout when opened in mobile device.
* mobileWidth - Max screen width to use mobile layout instead of default one.
* mobileAutoScroll - Automatically scroll the page if focused input element
* gets obstructed by color picker dialog.
* htmlOptions - Load options from HTML attributes.
* To set options using HTML attributes,
* prefix these options with 'data-wcp-' as attribute names.
* snap - Snap color wheel and slider on 0, 0.5, and 1.0
* snapTolerance - Snap if slider position falls within defined
* tolerance value.
*/
WCP.defaults = {
format: 'hex', /* 1.x */
preview: false, /* 1.x */
live: true, /* 2.0 */
userinput: true, /* DEPRECATED 1.x */
validate: true, /* 1.x */
autoResize: true, /* 3.0 */
autoFormat: true, /* 3.0 */
//color: null, /* DEPRECATED 1.x */ /* OBSOLETE 3.0 */ /* Init-time only */
//alpha: null, /* DEPRECATED 1.x */ /* OBSOLETE 3.0 */ /* See methods.alpha */
preserveWheel: null, /* DEPRECATED 1.x */ /* Use live */
cssClass: '', /* 2.0 */
layout: 'popup', /* 2.0 */ /* Init-time only */
animDuration: 200, /* 2.0 */
quality: 1, /* 2.0 */
sliders: null, /* 2.0 */
//sliderLabel: true, /* 2.0 */ /* NOT IMPLEMENTED */
//sliderValue: false, /* 2.0 */ /* NOT IMPLEMENTED */
rounding: 2, /* 2.3 */
mobile: true, /* 3.0 */
mobileWidth: 480, /* 3.0 */
hideKeyboard: false, /* 2.4 */
htmlOptions: true, /* 2.3 */
snap: false, /* 2.5 */
snapTolerance: 0.05 /* 2.5 */
};
/******************************************************************/
//////////////////////////////
// STATIC OBJECTS AND FLAGS //
//////////////////////////////
/*
* Note: To determine input position (top and left), use the following:
* WCP.ORIGIN.top + this.input.getBoundingClientRect().top
* instead of using $(this.input).offset().top because on mobile browsers
* (chrome) jQuery's offset() function returns wrong value.
*/
/// Top left of the page is not on (0,0), making window.scrollX/Y and offset() useless
/// See WCP.ORIGIN
WCP.BUG_RELATIVE_PAGE_ORIGIN = false;
/// Coordinate of the top left page (mobile chrome workaround)
WCP.ORIGIN = { left: 0, top: 0 };
/******************************************************************/
//////////////////////
// HELPER FUNCTIONS //
//////////////////////
/**
* Function: colorToStr
*
* Since 2.0
*
* Convert color object to string in specified format
*
* Available formats:
* - hex
* - hexa
* - css
* - cssa
* - rgb
* - rgb%
* - rgba
* - rgba%
* - hsv
* - hsv%
* - hsva
* - hsva%
* - hsb
* - hsb%
* - hsba
* - hsba%
*/
WCP.colorToStr = function( color, format ) {
var result = "";
switch( format ) {
case 'css':
result = "#";
case 'hex':
var r = Math.round(color.r * 255).toString(16);
if( r.length == 1) {
r = "0" + r;
}
var g = Math.round(color.g * 255).toString(16);
if( g.length == 1) {
g = "0" + g;
}
var b = Math.round(color.b * 255).toString(16);
if( b.length == 1) {
b = "0" + b;
}
result += r + g + b;
break;
case 'cssa':
result = "#";
case 'hexa':
var r = Math.round(color.r * 255).toString(16);
if( r.length == 1) {
r = "0" + r;
}
var g = Math.round(color.g * 255).toString(16);
if( g.length == 1) {
g = "0" + g;
}
var b = Math.round(color.b * 255).toString(16);
if( b.length == 1) {
b = "0" + b;
}
var a = Math.round(color.a * 255).toString(16);
if( a.length == 1) {
a = "0" + a;
}
result += r + g + b + a;
break;
case 'rgb':
result = "rgb(" +
Math.round(color.r * 255) + "," +
Math.round(color.g * 255) + "," +
Math.round(color.b * 255) + ")";
break;
case 'rgb%':
result = "rgb(" +
(color.r * 100) + "%," +
(color.g * 100) + "%," +
(color.b * 100) + "%)";
break;
case 'rgba':
result = "rgba(" +
Math.round(color.r * 255) + "," +
Math.round(color.g * 255) + "," +
Math.round(color.b * 255) + "," +
color.a + ")";
break;
case 'rgba%':
result = "rgba(" +
(color.r * 100) + "%," +
(color.g * 100) + "%," +
(color.b * 100) + "%," +
(color.a * 100) + "%)";
break;
case 'hsv':
result = "hsv(" +
(color.h * 360) + "," +
color.s + "," +
color.v + ")";
break;
case 'hsv%':
result = "hsv(" +
(color.h * 100) + "%," +
(color.s * 100) + "%," +
(color.v * 100) + "%)";
break;
case 'hsva':
result = "hsva(" +
(color.h * 360) + "," +
color.s + "," +
color.v + "," +
color.a + ")";
break;
case 'hsva%':
result = "hsva(" +
(color.h * 100) + "%," +
(color.s * 100) + "%," +
(color.v * 100) + "%," +
(color.a * 100) + "%)";
break;
case 'hsb':
result = "hsb(" +
color.h + "," +
color.s + "," +
color.v + ")";
break;
case 'hsb%':
result = "hsb(" +
(color.h * 100) + "%," +
(color.s * 100) + "%," +
(color.v * 100) + "%)";
break;
case 'hsba':
result = "hsba(" +
color.h + "," +
color.s + "," +
color.v + "," +
color.a + ")";
break;
case 'hsba%':
result = "hsba(" +
(color.h * 100) + "%," +
(color.s * 100) + "%," +
(color.v * 100) + "%," +
(color.a * 100) + "%)";
break;
}
return result;
};
/**
* Function: strToColor
*
* Since 2.0
*
* Convert string to color object.
* Please note that if RGB color is supplied, the returned value
* will only contain RGB.
*
* If invalid string is supplied, FALSE will be returned.
*/
WCP.strToColor = function( val ) {
var color = { a: 1 };
var tmp;
var hasAlpha;
// #fff
// #ffff
if(
val.match(/^#[0-9a-f]{3}$/i) != null ||
val.match(/^#[0-9a-f]{4}$/i)
) {
if( isNaN( color.r = parseInt(val.substr(1, 1), 16) * 17 / 255 ) ) {
return false;
}
if( isNaN( color.g = parseInt(val.substr(2, 1), 16) * 17 / 255 ) ) {
return false;
}
if( isNaN( color.b = parseInt(val.substr(3, 1), 16) * 17 / 255 ) ) {
return false;
}
// Alpha
if(val.length == 5) {
if( isNaN( color.a = parseInt(val.substr(4, 1), 16) * 17 / 255 ) ) {
return false;
}
}
}
// fff
// ffff
else if(
val.match(/^[0-9a-f]{3}$/i) != null ||
val.match(/^[0-9a-f]{4}$/i) != null
) {
if( isNaN( color.r = parseInt(val.substr(0, 1), 16) * 17 / 255 ) ) {
return false;
}
if( isNaN( color.g = parseInt(val.substr(1, 1), 16) * 17 / 255 ) ) {
return false;
}
if( isNaN( color.b = parseInt(val.substr(2, 1), 16) * 17 / 255 ) ) {
return false;
}
// Alpha
if(val.length == 4) {
if( isNaN( color.a = parseInt(val.substr(3, 1), 16) * 17 / 255 ) ) {
return false;
}
}
}
// #ffffff
// #ffffffff
else if(
val.match(/^#[0-9a-f]{6}$/i) != null ||
val.match(/^#[0-9a-f]{8}$/i) != null
) {
if( isNaN( color.r = parseInt(val.substr(1, 2), 16) / 255 ) ) {
return false;
}
if( isNaN( color.g = parseInt(val.substr(3, 2), 16) / 255 ) ) {
return false;
}
if( isNaN( color.b = parseInt(val.substr(5, 2), 16) / 255 ) ) {
return false;
}
// Alpha
if(val.length == 9) {
if( isNaN( color.a = parseInt(val.substr(7, 2), 16) / 255 ) ) {
return false;
}
}
}
// ffffff
// ffffffff
else if(
val.match(/^[0-9a-f]{6}$/i) != null ||
val.match(/^[0-9a-f]{8}$/i) != null
) {
if( isNaN( color.r = parseInt(val.substr(0, 2), 16) / 255 ) ) {
return false;
}
if( isNaN( color.g = parseInt(val.substr(2, 2), 16) / 255 ) ) {
return false;
}
if( isNaN( color.b = parseInt(val.substr(4, 2), 16) / 255 ) ) {
return false;
}
// Alpha
if(val.length == 8) {
if( isNaN( color.a = parseInt(val.substr(6, 2), 16) / 255 ) ) {
return false;
}
}
}
// rgb(100%,100%,100%)
// rgba(100%,100%,100%,100%)
// rgba(255,255,255,1)
// rgba(100%,1, 0.5,.2)
else if(
val.match(/^rgba\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null ||
val.match(/^rgb\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null
) {
if(val.match(/a/i) != null) {
hasAlpha = true;
}
else {
hasAlpha = false;
}
tmp = val.substring(val.indexOf('(')+1, val.indexOf(','));
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.r = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.r = parseInt(tmp) / 255 ) ) {
return false;
}
}
tmp = val.substring(val.indexOf(',')+1, val.indexOf(',', val.indexOf(',')+1));
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.g = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.g = parseInt(tmp) / 255 ) ) {
return false;
}
}
if(hasAlpha) {
tmp = val.substring(val.indexOf(',', val.indexOf(',')+1)+1, val.lastIndexOf(','));
}
else {
tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')'));
}
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.b = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.b = parseInt(tmp) / 255 ) ) {
return false;
}
}
if(hasAlpha) {
tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')'));
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.a = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.a = parseFloat(tmp) ) ) {
return false;
}
}
}
}
// hsv(100%,100%,100%)
// hsva(100%,100%,100%,100%)
// hsv(360,1,1,1)
// hsva(360,1, 0.5,.2)
// hsb(100%,100%,100%)
// hsba(100%,100%,100%,100%)
// hsb(360,1,1,1)
// hsba(360,1, 0.5,.2)
else if(
val.match(/^hsva\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null ||
val.match(/^hsv\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null ||
val.match(/^hsba\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null ||
val.match(/^hsb\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*\)$/i) != null
) {
if(val.match(/a/i) != null) {
hasAlpha = true;
}
else {
hasAlpha = false;
}
tmp = val.substring(val.indexOf('(')+1, val.indexOf(','));
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.h = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.h = parseFloat(tmp) / 360 ) ) {
return false;
}
}
tmp = val.substring(val.indexOf(',')+1, val.indexOf(',', val.indexOf(',')+1));
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.s = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.s = parseFloat(tmp) ) ) {
return false;
}
}
if(hasAlpha) {
tmp = val.substring(val.indexOf(',', val.indexOf(',')+1)+1, val.lastIndexOf(','));
}
else {
tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')'));
}
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.v = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.v = parseFloat(tmp) ) ) {
return false;
}
}
if(hasAlpha) {
tmp = val.substring(val.lastIndexOf(',')+1, val.lastIndexOf(')'));
if( tmp.charAt( tmp.length-1 ) == '%') {
if( isNaN( color.a = parseFloat(tmp) / 100 ) ) {
return false;
}
}
else {
if( isNaN( color.a = parseFloat(tmp) ) ) {
return false;
}
}
}
}
else {
return false;
}
return color;
};
/**
* Function: hsvToRgb
*
* Since 2.0
*
* Perform HSV to RGB conversion
*/
WCP.hsvToRgb = function( h, s, v ) {
// Calculate RGB from hue (1st phase)
var cr = (h <= (1/6) || h >= (5/6)) ? 1
: (h < (1/3) ? 1 - ((h - (1/6)) * 6)
: (h > (4/6) ? (h - (4/6)) * 6
: 0));
var cg = (h >= (1/6) && h <= (3/6)) ? 1
: (h < (1/6) ? h * 6
: (h < (4/6) ? 1 - ((h - (3/6)) * 6)
: 0));
var cb = (h >= (3/6) && h <= (5/6)) ? 1
: (h > (2/6) && h < (3/6) ? (h - (2/6)) * 6
: (h > (5/6) ? 1 - ((h - (5/6)) * 6)
: 0));
// console.log(cr + ' ' + cg + ' ' + cb);
// Calculate RGB with saturation & value applied
var r = (cr + (1-cr)*(1-s)) * v;
var g = (cg + (1-cg)*(1-s)) * v;
var b = (cb + (1-cb)*(1-s)) * v;
// console.log(r + ' ' + g + ' ' + b + ' ' + v);
return { r: r, g: g, b: b };
};
/**
* Function: rgbToHsv
*
* Since 2.0
*
* Perform RGB to HSV conversion
*/
WCP.rgbToHsv = function( r, g, b ) {
var h;
var s;
var v;
var maxColor = Math.max(r, g, b);
var minColor = Math.min(r, g, b);
var delta = maxColor - minColor;
// Calculate saturation
if(maxColor != 0) {
s = delta / maxColor;
}
else {
s = 0;
}
// Calculate hue
// To simplify the formula, we use 0-6 range.
if(delta == 0) {
h = 0;
}
else if(r == maxColor) {
h = (6 + (g - b) / delta) % 6;
}
else if(g == maxColor) {
h = 2 + (b - r) / delta;
}
else if(b == maxColor) {
h = 4 + (r - g) / delta;
}
else {
h = 0;
}
// Then adjust the range to be 0-1
h = h/6;
// Calculate value
v = maxColor;
// console.log(h + ' ' + s + ' ' + v);
return { h: h, s: s, v: v };
};
/******************************************************************/
////////////////////////
// COLOR PICKER CLASS //
////////////////////////
/**
* Class: ColorPicker
*
* Since 3.0
*/
WCP.ColorPicker = function ( elm, options ) {
// Assign reference to input DOM element
this.input = elm;
// Setup selected color in the following priority:
// 1. options.color
// 2. input.value
// 3. default
this.color = { h: 0, s: 0, v: 1, r: 1, g: 1, b: 1, a: 1 };
this.setValue(this.input.value);
// Set options
this.options = $.extend(true, {}, WCP.defaults);
this.setOptions(options);
// Check sliders option, if not defined, set default sliders
if(this.options.sliders == null)
this.options.sliders = 'wvp' + (this.options.format.indexOf('a') >= 0 ? 'a' : '');
this.init();
};
////////////////////
// Static members //
////////////////////
/**
* Static Property: ColorPicker.widget
*
* Reference to global color picker widget (popup)
*/
WCP.ColorPicker.widget = null;
/**
* Property: ColorPicker.overlay
*
* Reference to overlay DOM element (overlay for global popup)
*/
WCP.ColorPicker.overlay = null;
/**
* Function: init
*
* Since 3.0
* 2.0 was methods.staticInit
*
* Initialize wheel color picker globally.
*/
WCP.ColorPicker.init = function() {
// Only perform initialization once
if(WCP.ColorPicker.init.hasInit == true)
return;
WCP.ColorPicker.init.hasInit = true;
// Insert overlay element to handle popup closing
// when hideKeyboard is true, hence input is always blurred
var $overlay = $('<div class="jQWCP-overlay" style="display: none;"></div>');
$overlay.on('click', WCP.Handler.overlay_click);
WCP.ColorPicker.overlay = $overlay.get(0);
$('body').append($overlay);
// Insert CSS for color wheel
var wheelImage = WCP.ColorPicker.getWheelDataUrl(200);
$('head').append(
'<style type="text/css">' +
'.jQWCP-wWheel {' +
'background-image: url(' + wheelImage + ');' +
'}' +
'</style>'
);
// Attach events
$('html').on('mouseup.wheelColorPicker', WCP.Handler.html_mouseup);
$('html').on('touchend.wheelColorPicker', WCP.Handler.html_mouseup);
$('html').on('mousemove.wheelColorPicker', WCP.Handler.html_mousemove);
$('html').on('touchmove.wheelColorPicker', WCP.Handler.html_mousemove);
$(window).on('resize.wheelColorPicker', WCP.Handler.window_resize);
};
/**
* Function: createWidget
*
* Since 3.0
* 2.5 was private.initWidget
*
* Create color picker widget.
*/
WCP.ColorPicker.createWidget = function() {
/// WIDGET ///
// Notice: We won't use canvas to draw the color wheel since
// it may takes time and cause performance issue.
var $widget = $(
"<div class='jQWCP-wWidget'>" +
"<div class='jQWCP-wWheel'>" +
"<div class='jQWCP-wWheelOverlay'></div>" +
"<span class='jQWCP-wWheelCursor'></span>" +
"</div>" +
"<div class='jQWCP-wHue jQWCP-slider-wrapper'>" +
"<canvas class='jQWCP-wHueSlider jQWCP-slider' width='1' height='50' title='Hue'></canvas>" +
"<span class='jQWCP-wHueCursor jQWCP-scursor'></span>" +
"</div>" +
"<div class='jQWCP-wSat jQWCP-slider-wrapper'>" +
"<canvas class='jQWCP-wSatSlider jQWCP-slider' width='1' height='50' title='Saturation'></canvas>" +
"<span class='jQWCP-wSatCursor jQWCP-scursor'></span>" +
"</div>" +
"<div class='jQWCP-wVal jQWCP-slider-wrapper'>" +
"<canvas class='jQWCP-wValSlider jQWCP-slider' width='1' height='50' title='Value'></canvas>" +
"<span class='jQWCP-wValCursor jQWCP-scursor'></span>" +
"</div>" +
"<div class='jQWCP-wRed jQWCP-slider-wrapper'>" +
"<canvas class='jQWCP-wRedSlider jQWCP-slider' width='1' height='50' title='Red'></canvas>" +
"<span class='jQWCP-wRedCursor jQWCP-scursor'></span>" +
"</div>" +
"<div class='jQWCP-wGreen jQWCP-slider-wrapper'>" +
"<canvas class='jQWCP-wGreenSlider jQWCP-slider' width='1' height='50' title='Green'></canvas>" +
"<span class='jQWCP-wGreenCursor jQWCP-scursor'></span>" +
"</div>" +
"<div class='jQWCP-wBlue jQWCP-slider-wrapper'>" +
"<canvas class='jQWCP-wBlueSlider jQWCP-slider' width='1' height='50' title='Blue'></canvas>" +
"<span class='jQWCP-wBlueCursor jQWCP-scursor'></span>" +
"</div>" +
"<div class='jQWCP-wAlpha jQWCP-slider-wrapper'>" +
"<canvas class='jQWCP-wAlphaSlider jQWCP-slider' width='1' height='50' title='Alpha'></canvas>" +
"<span class='jQWCP-wAlphaCursor jQWCP-scursor'></span>" +
"</div>" +
"<div class='jQWCP-wPreview'>" +
"<canvas class='jQWCP-wPreviewBox' width='1' height='1' title='Selected Color'></canvas>" +
"</div>" +
"</div>"
);
// Small UI fix to disable highlighting the widget
// Also UI fix to disable touch context menu
$widget.find('.jQWCP-wWheel, .jQWCP-slider-wrapper, .jQWCP-scursor, .jQWCP-slider')
.attr('unselectable', 'on')
.css('-moz-user-select', 'none')
.css('-webkit-user-select', 'none')
.css('user-select', 'none')
.css('-webkit-touch-callout', 'none');
// Disable context menu on sliders
// Workaround for touch browsers
$widget.on('contextmenu.wheelColorPicker', function() { return false; });
// Bind widget events
$widget.on('mousedown.wheelColorPicker', '.jQWCP-wWheel', WCP.Handler.wheel_mousedown);
$widget.on('touchstart.wheelColorPicker', '.jQWCP-wWheel', WCP.Handler.wheel_mousedown);
$widget.on('mousedown.wheelColorPicker', '.jQWCP-wWheelCursor', WCP.Handler.wheelCursor_mousedown);
$widget.on('touchstart.wheelColorPicker', '.jQWCP-wWheelCursor', WCP.Handler.wheelCursor_mousedown);
$widget.on('mousedown.wheelColorPicker', '.jQWCP-slider', WCP.Handler.slider_mousedown);
$widget.on('touchstart.wheelColorPicker', '.jQWCP-slider', WCP.Handler.slider_mousedown);
$widget.on('mousedown.wheelColorPicker', '.jQWCP-scursor', WCP.Handler.sliderCursor_mousedown);
$widget.on('touchstart.wheelColorPicker', '.jQWCP-scursor', WCP.Handler.sliderCursor_mousedown);
return $widget.get(0);
};
/**
* Function: getWheelDataUrl
*
* Create color wheel image and return as base64 encoded data url.
*/
WCP.ColorPicker.getWheelDataUrl = function( size ) {
var r = size / 2; // radius
var center = r;
var canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
var context = canvas.getContext('2d');
// Fill the wheel with colors
for(var y = 0; y < size; y++) {
for(var x = 0; x < size; x++) {
// Get the offset from central position
var offset = Math.sqrt(Math.pow(x - center, 2) + Math.pow(y - center, 2));
// Skip pixels outside picture area (plus 2 pixels)
if(offset > r + 2) {
continue;
}
// Get the position in degree (hue)
var deg = (
(x - center == 0
? (y < center ? 90 : 270)
: (Math.atan((center - y) / (x - center)) / Math.PI * 180)
)
+ (x < center ? 180 : 0)
+ 360
) % 360;
// Relative offset (sat)
var sat = offset / r;
// Value is always 1
var val = 1;
// Calculate color
var cr = (Math.abs(deg + 360) + 60) % 360 < 120 ? 1
: (deg > 240 ? (120 - Math.abs(deg - 360)) / 60
: (deg < 120 ? (120 - deg) / 60
: 0));
var cg = Math.abs(deg - 120) < 60 ? 1
: (Math.abs(deg - 120) < 120 ? (120 - Math.abs(deg - 120)) / 60
: 0);
var cb = Math.abs(deg - 240) < 60 ? 1
: (Math.abs(deg - 240) < 120 ? (120 - Math.abs(deg - 240)) / 60
: 0);
var pr = Math.round((cr + (1 - cr) * (1 - sat)) * 255);
var pg = Math.round((cg + (1 - cg) * (1 - sat)) * 255);
var pb = Math.round((cb + (1 - cb) * (1 - sat)) * 255);
context.fillStyle = 'rgb(' + pr + ',' + pg + ',' + pb + ')';
context.fillRect(x, y, 1, 1);
}
}
return canvas.toDataURL();
};
/////////////
// Members //
/////////////
/**
* Property: ColorPicker.options
*
* Plugin options for the color picker instance, extended from WCP.defaults.
*/
WCP.ColorPicker.prototype.options = null;
/**
* Property: ColorPicker.input
*
* Reference to input DOM element
*/
WCP.ColorPicker.prototype.input = null;
/**
* Property: ColorPicker.widget
*
* Reference to widget DOM element (global popup or private inline widget)
*/
WCP.ColorPicker.prototype.widget = null;
/**
* Property: ColorPicker.color
*
* Selected color object.
*/
WCP.ColorPicker.prototype.color = null;
/**
* Property: ColorPicker.lastValue
*
* Store last input value
*/
WCP.ColorPicker.prototype.lastValue = null;
/**
* Function: ColorPicker.setOptions
*
* Since 3.0
*
* Set options to the color picker. If htmlOptions is set to true,
* options set via html attributes are also reloaded. If both html
* attribute and argument exists, option set via options argument
* gets priority.
*/
WCP.ColorPicker.prototype.setOptions = function( options ) {
// options should be a separate object (passed by value)
// Make a copy of options
options = $.extend(true, {}, options);
// Load options from HTML attributes
if(this.options.htmlOptions) {
for(var key in WCP.defaults) {
// Only if option key is valid and not set via function argument
if(this.input.hasAttribute('data-wcp-'+key) && options[key] === undefined) {
options[key] = this.input.getAttribute('data-wcp-'+key);
// Change true/false string to boolean
if(options[key] == 'true') {
options[key] = true;
}
else if(options[key] == 'false') {
options[key] = false;
}
}
}
}
// Set options
for(var key in options) {
// Skip undefined option key
if(this.options[key] === undefined)
continue;
var keyUc = key.charAt(0).toUpperCase() + key.slice(1);
// If setter is available, try setting it via setter
if(typeof this['set'+keyUc] === "function") {
this['set'+keyUc](options[key]);
}
// Otherwise directly update options
else {
this.options[key] = options[key];
}
}
return this; // Allow chaining
};
/**
* Function: ColorPicker.init
*
* Initialize wheel color picker widget
*/
WCP.ColorPicker.prototype.init = function() {
WCP.ColorPicker.init();
// Initialization must only occur once
if(this.hasInit == true)
return;
this.hasInit = true;
var instance = this;
var $input = $(this.input);
var $widget = null;
/// LAYOUT & BINDINGS ///
// Setup block mode layout
if( this.options.layout == 'block' ) {
// Create widget
this.widget = WCP.ColorPicker.createWidget();
$widget = $(this.widget);
// Store object instance reference
$widget.data('jQWCP.instance', this);
// Wrap widget around the input elm and put the input
// elm inside widget
$widget.insertAfter(this.input);
// Retain display CSS property
if($input.css('display') == "inline") {
$widget.css('display', "inline-block");
}
else {
$widget.css('display', $input.css('display'));
}
$widget.append(this.input);
$input.hide();
// Add tabindex attribute to make the widget focusable
if($input.attr('tabindex') != undefined) {
$widget.attr('tabindex', $input.attr('tabindex'));
}
else {
$widget.attr('tabindex', 0);
}
// Further widget adjustments based on options
this.refreshWidget();
// Draw shading
this.redrawSliders(true);
this.updateSliders();
// Bind widget element events
$widget.on('focus.wheelColorPicker', WCP.Handler.widget_focus_block);
$widget.on('blur.wheelColorPicker', WCP.Handler.widget_blur_block);
}
// Setup popup mode layout
else {
// Only need to create one widget, used globally
if(WCP.ColorPicker.widget == null) {
WCP.ColorPicker.widget = WCP.ColorPicker.createWidget();
$widget = $(WCP.ColorPicker.widget);
$widget.attr('id', 'jQWCP-popup');
// Assign widget to global
$widget.hide();
$('body').append($widget);
// Bind popup events
$widget.on('mousedown.wheelColorPicker', WCP.Handler.widget_mousedown_popup);
//$widget.on('mouseup.wheelColorPicker', WCP.Handler.widget_mouseup_popup);
}
this.widget = WCP.ColorPicker.widget;
// Bind input element events
$input.on('focus.wheelColorPicker', WCP.Handler.input_focus_popup);
$input.on('blur.wheelColorPicker', WCP.Handler.input_blur_popup);
}
// Bind input events
$input.on('keyup.wheelColorPicker', WCP.Handler.input_keyup);
$input.on('change.wheelColorPicker', WCP.Handler.input_change);
// Set color value
// DEPRECATED by 3.0
if(typeof this.options.color == "object") {
this.setColor(this.options.color);
this.options.color = undefined;
}
else if(typeof this.options.color == "string") {
this.setValue(this.options.color);
this.options.color = undefined;
}
// Set readonly mode
/* DEPRECATED */
if(this.options.userinput) {
$input.removeAttr('readonly');
}
else {
$input.attr('readonly', true);
}
};
/**
* Function: destroy
*
* Destroy the color picker and return it to normal element.
*/
WCP.ColorPicker.prototype.destroy = function() {
var $widget = $(this.widget);
var $input = $(this.input);
// Reset layout
// No need to delete global popup
if(this.options.layout == 'block') {
// Check if active control is the same widget as destroyed widget, remove the reference if it's true
var $control = $( $('body').data('jQWCP.activeControl') ); // Refers to slider wrapper or wheel
if ($control.length) {
var controlWidget = $control.closest('.jQWCP-wWidget');
if ($widget.is(controlWidget)) {
$('body').data('jQWCP.activeControl', null);
}
}
$widget.before(this.input);
$widget.remove();
$input.show();
}
// Unbind events
$input.off('focus.wheelColorPicker');
$input.off('blur.wheelColorPicker');
$input.off('keyup.wheelColorPicker');
$input.off('change.wheelColorPicker');
// Remove data
$input.data('jQWCP.instance', null);
// remove self
delete this;
};
/**
* Function: refreshWidget
*
* Since 3.0
* 2.5 was private.adjustWidget
*
* Update widget to match current option values.
*/
WCP.ColorPicker.prototype.refreshWidget = function() {
var $widget = $(this.widget);
var options = this.options;
var mobileLayout = false;
// Set CSS classes
$widget.attr('class', 'jQWCP-wWidget');
if(options.layout == 'block') {
$widget.addClass('jQWCP-block');
}
$widget.addClass(options.cssClass);
//$widget.addClass(this.input.getAttribute('class'));
// Check whether to use mobile layout
if(window.innerWidth <= options.mobileWidth && options.layout != 'block' && options.mobile) {
mobileLayout = true;
$widget.addClass('jQWCP-mobile');
}
// Rearrange sliders
$widget.find('.jQWCP-wWheel, .jQWCP-slider-wrapper, .jQWCP-wPreview')
.hide()
.addClass('hidden');
for(var i in options.sliders) {
var $slider = null;
switch(this.options.sliders[i]) {
case 'w':
$slider = $widget.find('.jQWCP-wWheel'); break;
case 'h':
$slider = $widget.find('.jQWCP-wHue'); break;
case 's':
$slider = $widget.find('.jQWCP-wSat'); break;
case 'v':
$slider = $widget.find('.jQWCP-wVal'); break;
case 'r':
$slider = $widget.find('.jQWCP-wRed'); break;
case 'g':
$slider = $widget.find('.jQWCP-wGreen'); break;
case 'b':
$slider = $widget.find('.jQWCP-wBlue'); break;
case 'a':
$slider = $widget.find('.jQWCP-wAlpha'); break;
case 'p':
$slider = $widget.find('.jQWCP-wPreview'); break;
}
if($slider != null) {
$slider.appendTo(this.widget);
$slider.show().removeClass('hidden');
}
}
// If widget is hidden, show it first so we can calculate dimensions correctly
//var widgetIsHidden = false;
//if($widget.is(':hidden')) {
//widgetIsHidden = true;
//$widget.css({ opacity: '0' }).show();
//}
// Adjust sliders height based on quality
var sliderHeight = options.quality * 50;
$widget.find('.jQWCP-slider').attr('height', sliderHeight);
var $visElms = $widget.find('.jQWCP-wWheel, .jQWCP-slider-wrapper, .jQWCP-wPreview').not('.hidden');
// Adjust container and sliders width
// Only if not on mobile layout (force fixed on mobile)
if(options.autoResize && !mobileLayout) {
// Auto resize
var width = 0
// Set slider size first, then adjust container
$visElms.css({ width: '', height: '' });
$visElms.each(function(index, item) {
var $item = $(item);
width += parseFloat($item.css('margin-left').replace('px', '')) + parseFloat($item.css('margin-right').replace('px', '')) + $item.outerWidth();
});
$widget.css({ width: width + 'px' });
}
else {
// Fixed size
// Set container size first, then adjust sliders
$widget.css({ width: '' });
var $visWheel = $widget.find('.jQWCP-wWheel').not('.hidden');
var $visSliders = $widget.find('.jQWCP-slider-wrapper, .jQWCP-wPreview').not('.hidden');
$visWheel.css({ height: $widget.height() + 'px', width: $widget.height() });
if($visWheel.length > 0) {
var horzSpace = $widget.width() - $visWheel.outerWidth() - parseFloat($visWheel.css('margin-left').replace('px', '')) - parseFloat($visWheel.css('margin-right').replace('px', ''));
}
else {
var horzSpace = $widget.width();
}
if($visSliders.length > 0) {
var sliderMargins = parseFloat($visSliders.css('margin-left').replace('px', '')) + parseFloat($visSliders.css('margin-right').replace('px', ''));
$visSliders.css({ height: $widget.height() + 'px', width: (horzSpace - ($visSliders.length - 1) * sliderMargins) / $visSliders.length + 'px' });
}
}
// Reset visibility
//if(widgetIsHidden) {
//$widget.css({ opacity: '' }).hide();
//}
return this; // Allows method chaining
};
/**
* Function: redrawSliders
*
* Introduced in 2.0
*
* Redraw slider gradients. Hidden sliders are not redrawn as to
* improve performance. If options.live is FALSE, sliders are not redrawn.
*
* Parameter:
* force - Boolean force redraw all sliders.
*/
WCP.ColorPicker.prototype.redrawSliders = function( force ) {
// Skip if widget not yet initialized
if(this.widget == null)
return this;
var $widget = $(this.widget);
// DEPRECATED 3.0
// In 2.0, parameters are ( sliders, force )
if(typeof arguments[0] === "string") {
force = arguments[1];
}
// No need to redraw sliders on global popup widget if not
// attached to the input elm in current iteration
if(this != $widget.data('jQWCP.instance'))
return this;
var options = this.options;
var color = this.color;
var w = 1;
var h = options.quality * 50;
var A = 1;
var R = 0;
var G = 0;
var B = 0;
var H = 0;
var S = 0;
var V = 1;
// Dynamic colors
if(options.live) {
A = color.a;
R = Math.round(color.r * 255);
G = Math.round(color.g * 255);
B = Math.round(color.b * 255);
H = color.h;
S = color.s;
V = color.v;
}
/// PREVIEW ///
// Preview box must always be redrawn, if not hidden
var $previewBox = $widget.find('.jQWCP-wPreviewBox');
if(!$previewBox.hasClass('hidden')) {
var previewBoxCtx = $previewBox.get(0).getContext('2d');
previewBoxCtx.fillStyle = "rgba(" + R + "," + G + "," + B + "," + A + ")";
previewBoxCtx.clearRect(0, 0, 1, 1);
previewBoxCtx.fillRect(0, 0, 1, 1);
}
/// SLIDERS ///
if(!this.options.live && !force)
return this;
/// ALPHA ///
// The top color is (R, G, B, 1)
// The bottom color is (R, G, B, 0)
var $alphaSlider = $widget.find('.jQWCP-wAlphaSlider');
if(!$alphaSlider.hasClass('hidden') || force) {
var alphaSliderCtx = $alphaSlider.get(0).getContext('2d');
var alphaGradient = alphaSliderCtx.createLinearGradient(0, 0, 0, h);
alphaGradient.addColorStop(0, "rgba("+R+","+G+","+B+",1)");
alphaGradient.addColorStop(1, "rgba("+R+","+G+","+B+",0)");
alphaSliderCtx.fillStyle = alphaGradient;
alphaSliderCtx.clearRect(0, 0, w, h);
alphaSliderCtx.fillRect(0, 0, w, h);
}
/// RED ///
// The top color is (255, G, B)
// The bottom color is (0, G, B)
var $redSlider = $widget.find('.jQWCP-wRedSlider');
if(!$redSlider.hasClass('hidden') || force) {
var redSliderCtx = $redSlider.get(0).getContext('2d');
var redGradient = redSliderCtx.createLinearGradient(0, 0, 0, h);
redGradient.addColorStop(0, "rgb(255,"+G+","+B+")");
redGradient.addColorStop(1, "rgb(0,"+G+","+B+")");
redSliderCtx.fillStyle = redGradient;
redSliderCtx.fillRect(0, 0, w, h);
}
/// GREEN ///
// The top color is (R, 255, B)
// The bottom color is (R, 0, B)
var $greenSlider = $widget.find('.jQWCP-wGreenSlider');
if(!$greenSlider.hasClass('hidden') || force) {
var greenSliderCtx = $greenSlider.get(0).getContext('2d');
var greenGradient = greenSliderCtx.createLinearGradient(0, 0, 0, h);
greenGradient.addColorStop(0, "rgb("+R+",255,"+B+")");
greenGradient.addColorStop(1, "rgb("+R+",0,"+B+")");
greenSliderCtx.fillStyle = greenGradient;
greenSliderCtx.fillRect(0, 0, w, h);
}
/// BLUE ///
// The top color is (R, G, 255)
// The bottom color is (R, G, 0)
var $blueSlider = $widget.find('.jQWCP-wBlueSlider');
if(!$blueSlider.hasClass('hidden') || force) {
var blueSliderCtx = $blueSlider.get(0).getContext('2d');
var blueGradient = blueSliderCtx.createLinearGradient(0, 0, 0, h);
blueGradient.addColorStop(0, "rgb("+R+","+G+",255)");
blueGradient.addColorStop(1, "rgb("+R+","+G+",0)");
blueSliderCtx.fillStyle = blueGradient;
blueSliderCtx.fillRect(0, 0, w, h);
}
/// HUE ///
// The hue slider is static.
var $hueSlider = $widget.find('.jQWCP-wHueSlider');
if(!$hueSlider.hasClass('hidden') || force) {
var hueSliderCtx = $hueSlider.get(0).getContext('2d');
var hueGradient = hueSliderCtx.createLinearGradient(0, 0, 0, h);
hueGradient.addColorStop(0, "#f00");
hueGradient.addColorStop(0.166666667, "#ff0");
hueGradient.addColorStop(0.333333333, "#0f0");
hueGradient.addColorStop(0.5, "#0ff");
hueGradient.addColorStop(0.666666667, "#00f");
hueGradient.addColorStop(0.833333333, "#f0f");
hueGradient.addColorStop(1, "#f00");
hueSliderCtx.fillStyle = hueGradient;
hueSliderCtx.fillRect(0, 0, w, h);
}
/// SAT ///
// The top color is hsv(h, 1, v)
// The bottom color is hsv(0, 0, v)
var $satSlider = $widget.find('.jQWCP-wSatSlider');
if(!$satSlider.hasClass('hidden') || force) {
var satTopRgb = $.fn.wheelColorPicker.hsvToRgb(H, 1, V);
satTopRgb.r = Math.round(satTopRgb.r * 255);
satTopRgb.g = Math.round(satTopRgb.g * 255);
satTopRgb.b = Math.round(satTopRgb.b * 255);
var satSliderCtx = $satSlider.get(0).getContext('2d');
var satGradient = satSliderCtx.createLinearGradient(0, 0, 0, h);
satGradient.addColorStop(0, "rgb("+satTopRgb.r+","+satTopRgb.g+","+satTopRgb.b+")");
satGradient.addColorStop(1, "rgb("+Math.round(V*255)+","+Math.round(V*255)+","+Math.round(V*255)+")");
satSliderCtx.fillStyle = satGradient;
satSliderCtx.fillRect(0, 0, w, h);
}
/// VAL ///
// The top color is hsv(h, s, 1)
// The bottom color is always black.
var $valSlider = $widget.find('.jQWCP-wValSlider');
if(!$valSlider.hasClass('hidden') || force) {
var valTopRgb = $.fn.wheelColorPicker.hsvToRgb(H, S, 1);
valTopRgb.r = Math.round(valTopRgb.r * 255);
valTopRgb.g = Math.round(valTopRgb.g * 255);
valTopRgb.b = Math.round(valTopRgb.b * 255);
var valSliderCtx = $valSlider.get(0).getContext('2d');
var valGradient = valSliderCtx.createLinearGradient(0, 0, 0, h);
valGradient.addColorStop(0, "rgb("+valTopRgb.r+","+valTopRgb.g+","+valTopRgb.b+")");
valGradient.addColorStop(1, "#000");
valSliderCtx.fillStyle = valGradient;
valSliderCtx.fillRect(0, 0, w, h);
}
return this; // Allows method chaining
};
/**
* Function: updateSliders
*
* Introduced in 2.0
*
* Update slider cursor positions based on this.color value.
* Only displayed sliders are updated. This function shall be called when widget is displayed
* so positions could be determined properly.
*/
WCP.ColorPicker.prototype.updateSliders = function() {
// Skip if not yet initialized
if(this.widget == null)
return this;
var $widget = $(this.widget);
var color = this.color;
// No need to redraw sliders on global popup widget if not
// attached to the input elm in current iteration
if(this != $widget.data('jQWCP.instance'))
return this;
// Wheel
var $wheel = $widget.find('.jQWCP-wWheel');
if(!$wheel.hasClass('hidden')) {
var $wheelCursor = $widget.find('.jQWCP-wWheelCursor');
var $wheelOverlay