Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Several usability improvements, especially for dragging mouse along colors #42

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 96 additions & 61 deletions colorpicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

// This HTML snippet is inserted into the innerHTML property of the passed color picker element
// when the no-hassle call to ColorPicker() is used, i.e. ColorPicker(function(hex, hsv, rgb) { ... });

var colorpickerHTMLSnippet = [

'<div class="picker-wrapper">',
'<div class="picker"></div>',
'<div class="picker-indicator"></div>',
Expand All @@ -20,24 +20,37 @@
'<div class="slide"></div>',
'<div class="slide-indicator"></div>',
'</div>'

].join('');

/**
* Return mouse position relative to the element el.
* Return mouse position relative to the element el, optionally
* clipping the coordinates to be inside the element
*/
function mousePosition(evt) {
// IE:
if (window.event && window.event.contentOverflow !== undefined) {
return { x: window.event.offsetX, y: window.event.offsetY };
function mousePosition(evt, el, clip) {
var posx = 0, posy = 0, result,
bounds = el.getBoundingClientRect();
// https://www.quirksmode.org/js/events_properties.html#position
evt = evt || window.event;
if (evt.pageX || evt.pageY) {
posx = evt.pageX;
posy = evt.pageY;
}
// Webkit:
if (evt.offsetX !== undefined && evt.offsetY !== undefined) {
return { x: evt.offsetX, y: evt.offsetY };
else if (evt.clientX || evt.clientY) {
posx = evt.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft;
posy = evt.clientY + document.body.scrollTop
+ document.documentElement.scrollTop;
}
// Firefox:
var wrapper = evt.target.parentNode.parentNode;
return { x: evt.layerX - wrapper.offsetLeft, y: evt.layerY - wrapper.offsetTop };
result = {
x: posx - bounds.left,
y: posy - bounds.top
};
if (clip) {
result.x = Math.max(0, Math.min(el.offsetWidth, result.x));
result.y = Math.max(0, Math.min(el.offsetHeight, result.y));
}
return result;
}

/**
Expand Down Expand Up @@ -122,7 +135,7 @@
'</v:rect>',
'</DIV>'
].join('');

if (!document.namespaces['v'])
document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
}
Expand All @@ -134,7 +147,7 @@
function hsv2rgb(hsv) {
var R, G, B, X, C;
var h = (hsv.h % 360) / 60;

C = hsv.v * hsv.s;
X = C * (1 - Math.abs(h % 2 - 1));
R = G = B = hsv.v - C;
Expand All @@ -160,7 +173,7 @@
var r = rgb.r;
var g = rgb.g;
var b = rgb.b;

if (rgb.r > 1 || rgb.g > 1 || rgb.b > 1) {
r /= 255;
g /= 255;
Expand All @@ -182,11 +195,11 @@
/**
* Return click event handler for the slider.
* Sets picker background color and calls ctx.callback if provided.
*/
*/
function slideListener(ctx, slideElement, pickerElement) {
return function(evt) {
evt = evt || window.event;
var mouse = mousePosition(evt);
var mouse = mousePosition(evt, slideElement, true);
ctx.h = mouse.y / slideElement.offsetHeight * 360 + hueOffset;
var pickerColor = hsv2rgb({ h: ctx.h, s: 1, v: 1 });
var c = hsv2rgb({ h: ctx.h, s: ctx.s, v: ctx.v });
Expand All @@ -198,12 +211,12 @@
/**
* Return click event handler for the picker.
* Calls ctx.callback if provided.
*/
*/
function pickerListener(ctx, pickerElement) {
return function(evt) {
evt = evt || window.event;
var mouse = mousePosition(evt),
width = pickerElement.offsetWidth,
var mouse = mousePosition(evt, pickerElement, true),
width = pickerElement.offsetWidth,
height = pickerElement.offsetHeight;

ctx.s = mouse.x / width;
Expand All @@ -214,15 +227,15 @@
};

var uniqID = 0;

/**
* ColorPicker.
* @param {DOMElement} slideElement HSV slide element.
* @param {DOMElement} pickerElement HSV picker element.
* @param {Function} callback Called whenever the color is changed provided chosen color in RGB HEX format as the only argument.
*/
function ColorPicker(slideElement, pickerElement, callback) {

if (!(this instanceof ColorPicker)) return new ColorPicker(slideElement, pickerElement, callback);

this.h = 0;
Expand All @@ -234,23 +247,23 @@

var element = slideElement;
element.innerHTML = colorpickerHTMLSnippet;

this.slideElement = element.getElementsByClassName('slide')[0];
this.pickerElement = element.getElementsByClassName('picker')[0];
var slideIndicator = element.getElementsByClassName('slide-indicator')[0];
var pickerIndicator = element.getElementsByClassName('picker-indicator')[0];

ColorPicker.fixIndicators(slideIndicator, pickerIndicator);

this.callback = function(hex, hsv, rgb, pickerCoordinate, slideCoordinate) {

ColorPicker.positionIndicators(slideIndicator, pickerIndicator, slideCoordinate, pickerCoordinate);

pickerElement(hex, hsv, rgb);
};

} else {

this.callback = callback;
this.pickerElement = pickerElement;
this.slideElement = slideElement;
Expand All @@ -263,32 +276,32 @@

var slideClone = slide.cloneNode(true);
var pickerClone = picker.cloneNode(true);

var hsvGradient = slideClone.getElementsByTagName('linearGradient')[0];

var hsvRect = slideClone.getElementsByTagName('rect')[0];

hsvGradient.id = 'gradient-hsv-' + uniqID;
hsvRect.setAttribute('fill', 'url(#' + hsvGradient.id + ')');

var blackAndWhiteGradients = [pickerClone.getElementsByTagName('linearGradient')[0], pickerClone.getElementsByTagName('linearGradient')[1]];
var whiteAndBlackRects = pickerClone.getElementsByTagName('rect');

blackAndWhiteGradients[0].id = 'gradient-black-' + uniqID;
blackAndWhiteGradients[1].id = 'gradient-white-' + uniqID;

whiteAndBlackRects[0].setAttribute('fill', 'url(#' + blackAndWhiteGradients[1].id + ')');
whiteAndBlackRects[1].setAttribute('fill', 'url(#' + blackAndWhiteGradients[0].id + ')');

this.slideElement.appendChild(slideClone);
this.pickerElement.appendChild(pickerClone);

uniqID++;

} else {

this.slideElement.innerHTML = slide;
this.pickerElement.innerHTML = picker;
this.pickerElement.innerHTML = picker;
}

addEventListener(this.slideElement, 'click', slideListener(this, this.slideElement, this.pickerElement));
Expand All @@ -301,35 +314,57 @@
function addEventListener(element, event, listener) {

if (element.attachEvent) {

element.attachEvent('on' + event, listener);

} else if (element.addEventListener) {

element.addEventListener(event, listener, false);
}
}

function removeEventListener(element, event, listener) {

if (element.detachEvent) {

element.detachEvent('on' + event, listener);

} else if (element.removeEventListener) {

element.removeEventListener(event, listener, false);
}
}

/**
* Enable drag&drop color selection.
* @param {object} ctx ColorPicker instance.
* @param {DOMElement} element HSV slide element or HSV picker element.
* @param {Function} listener Function that will be called whenever mouse is dragged over the element with event object as argument.
*/
function enableDragging(ctx, element, listener) {

var mousedown = false;

addEventListener(element, 'mousedown', function(evt) { mousedown = true; });
addEventListener(element, 'mouseup', function(evt) { mousedown = false; });
addEventListener(element, 'mouseout', function(evt) { mousedown = false; });
addEventListener(element, 'mousemove', function(evt) {
var start = function() {
removeEventListener(element, 'mousedown', start);
addEventListener(document.body, 'mouseup', stop);
addEventListener(document.body, 'mousemove', listener);
};

var stop = function() {
addEventListener(element, 'mousedown', start);
removeEventListener(document.body, 'mouseup', stop);
removeEventListener(document.body, 'mousemove', listener);
};

if (mousedown) {

listener(evt);
addEventListener(element, 'mousedown', start);
addEventListener(element, 'dragstart', function(evt) {
evt = evt || window.event;
if (evt.preventDefault) {
evt.preventDefault();
} else {
evt.returnValue = false;
}
});

}


Expand All @@ -338,21 +373,21 @@
delete rgbHex.hex;
return rgbHex;
};

ColorPicker.hsv2hex = function(hsv) {
return hsv2rgb(hsv).hex;
};

ColorPicker.rgb2hsv = rgb2hsv;

ColorPicker.rgb2hex = function(rgb) {
return hsv2rgb(rgb2hsv(rgb)).hex;
};

ColorPicker.hex2hsv = function(hex) {
return rgb2hsv(ColorPicker.hex2rgb(hex));
};

ColorPicker.hex2rgb = function(hex) {
return { r: parseInt(hex.substr(1, 2), 16), g: parseInt(hex.substr(3, 2), 16), b: parseInt(hex.substr(5, 2), 16) };
};
Expand All @@ -368,24 +403,24 @@
ctx.h = hsv.h % 360;
ctx.s = hsv.s;
ctx.v = hsv.v;

var c = hsv2rgb(ctx);

var mouseSlide = {
y: (ctx.h * ctx.slideElement.offsetHeight) / 360,
x: 0 // not important
};

var pickerHeight = ctx.pickerElement.offsetHeight;

var mousePicker = {
x: ctx.s * ctx.pickerElement.offsetWidth,
y: pickerHeight - ctx.v * pickerHeight
};

ctx.pickerElement.style.backgroundColor = hsv2rgb({ h: ctx.h, s: 1, v: 1 }).hex;
ctx.callback && ctx.callback(hex || c.hex, { h: ctx.h, s: ctx.s, v: ctx.v }, rgb || { r: c.r, g: c.g, b: c.b }, mousePicker, mouseSlide);

return ctx;
};

Expand All @@ -396,7 +431,7 @@
ColorPicker.prototype.setHsv = function(hsv) {
return setColor(this, hsv);
};

/**
* Sets color of the picker in rgb format.
* @param {object} rgb Object of the form: { r: <red>, g: <green>, b: <blue> }.
Expand All @@ -421,14 +456,14 @@
* @param {object} mousePicker Coordinates of the mouse cursor in the picker area.
*/
ColorPicker.positionIndicators = function(slideIndicator, pickerIndicator, mouseSlide, mousePicker) {

if (mouseSlide) {
slideIndicator.style.top = (mouseSlide.y - slideIndicator.offsetHeight/2) + 'px';
}
if (mousePicker) {
pickerIndicator.style.top = (mousePicker.y - pickerIndicator.offsetHeight/2) + 'px';
pickerIndicator.style.left = (mousePicker.x - pickerIndicator.offsetWidth/2) + 'px';
}
}
};

/**
Expand Down