From ea9315a29d644fb6d84992d677c35599899c0723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necdet=20Can=20Ate=C5=9Fman?= Date: Thu, 7 Sep 2017 14:31:48 +0200 Subject: [PATCH 1/5] Remove all trailing whitespace No functional changes, just removed all whitespace at the end of each line. --- colorpicker.js | 82 +++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/colorpicker.js b/colorpicker.js index e13892c..6534adf 100644 --- a/colorpicker.js +++ b/colorpicker.js @@ -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 = [ - + '
', '
', '
', @@ -20,7 +20,7 @@ '
', '
', '
' - + ].join(''); /** @@ -122,7 +122,7 @@ '', '' ].join(''); - + if (!document.namespaces['v']) document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); } @@ -134,7 +134,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; @@ -160,7 +160,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; @@ -182,7 +182,7 @@ /** * 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; @@ -198,12 +198,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, + width = pickerElement.offsetWidth, height = pickerElement.offsetHeight; ctx.s = mouse.x / width; @@ -214,7 +214,7 @@ }; var uniqID = 0; - + /** * ColorPicker. * @param {DOMElement} slideElement HSV slide element. @@ -222,7 +222,7 @@ * @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; @@ -234,23 +234,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; @@ -263,20 +263,20 @@ 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 + ')'); @@ -284,11 +284,11 @@ 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)); @@ -301,9 +301,9 @@ function addEventListener(element, event, listener) { if (element.attachEvent) { - + element.attachEvent('on' + event, listener); - + } else if (element.addEventListener) { element.addEventListener(event, listener, false); @@ -317,7 +317,7 @@ * @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; }); @@ -326,7 +326,7 @@ addEventListener(element, 'mousemove', function(evt) { if (mousedown) { - + listener(evt); } }); @@ -338,21 +338,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) }; }; @@ -368,24 +368,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; }; @@ -396,7 +396,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: , g: , b: }. @@ -421,14 +421,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'; - } + } }; /** From ebe72740f108b2f6aced35d0cf807efda8624a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necdet=20Can=20Ate=C5=9Fman?= Date: Thu, 7 Sep 2017 14:33:33 +0200 Subject: [PATCH 2/5] Register events in enableDragging() only on demand The function was previously registering all events and selectively performing operations based on a stored state. Changed the behaviour to register and unregister relevant event listeners on specific occasions. This not only removes explicit state, but also grants more flexibility, which will be required for future changes. --- colorpicker.js | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/colorpicker.js b/colorpicker.js index 6534adf..369512e 100644 --- a/colorpicker.js +++ b/colorpicker.js @@ -310,6 +310,18 @@ } } + 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. @@ -318,18 +330,22 @@ */ function enableDragging(ctx, element, listener) { - var mousedown = false; + var start = function() { + removeEventListener(element, 'mousedown', start); + addEventListener(element, 'mouseup', stop); + addEventListener(element, 'mouseout', stop); + addEventListener(element, 'mousemove', listener); + }; - 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 stop = function() { + addEventListener(element, 'mousedown', start); + removeEventListener(element, 'mouseup', stop); + removeEventListener(element, 'mouseout', stop); + removeEventListener(element, 'mousemove', listener); + }; - if (mousedown) { + addEventListener(element, 'mousedown', start); - listener(evt); - } - }); } From daa804a477282e7fef973019c742564f15685a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necdet=20Can=20Ate=C5=9Fman?= Date: Thu, 7 Sep 2017 14:34:23 +0200 Subject: [PATCH 3/5] Prevent native drag events during custom dragging Could not pinpoint why, but sometimes a native drag event triggers while sliding the mouse along the palette and suddenly the whole palette is being dragged with the mouse. This commit tries to negate this effect by adding a listener, that attempts to prevent the `dragstart` event. --- colorpicker.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/colorpicker.js b/colorpicker.js index 369512e..e25f3b2 100644 --- a/colorpicker.js +++ b/colorpicker.js @@ -345,6 +345,14 @@ }; addEventListener(element, 'mousedown', start); + addEventListener(element, 'dragstart', function(evt) { + evt = evt || window.event; + if (evt.preventDefault) { + evt.preventDefault(); + } else { + evt.returnValue = false; + } + }); } From 74b3889ed8a3db46ca7ef14c4970330bb4004a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necdet=20Can=20Ate=C5=9Fman?= Date: Thu, 7 Sep 2017 14:35:40 +0200 Subject: [PATCH 4/5] Don't stop dragging on mouse leave It feels more robust if the dragging continues even if the mouse leaves the area that contains the drag listener. This makes it easier to pick colors near the edges (particularly near *two* edges, i.e. in the corners) For this to work, another listener had to be altered: the `mouseup` listener is now registered on document.body, instead of the drag area. It is also unregistered once the dragging is complete. --- colorpicker.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/colorpicker.js b/colorpicker.js index e25f3b2..590389a 100644 --- a/colorpicker.js +++ b/colorpicker.js @@ -332,15 +332,13 @@ var start = function() { removeEventListener(element, 'mousedown', start); - addEventListener(element, 'mouseup', stop); - addEventListener(element, 'mouseout', stop); + addEventListener(document.body, 'mouseup', stop); addEventListener(element, 'mousemove', listener); }; var stop = function() { addEventListener(element, 'mousedown', start); - removeEventListener(element, 'mouseup', stop); - removeEventListener(element, 'mouseout', stop); + removeEventListener(document.body, 'mouseup', stop); removeEventListener(element, 'mousemove', listener); }; From a29f32262d5a73c86aa8f2724f2e525533e725ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necdet=20Can=20Ate=C5=9Fman?= Date: Thu, 7 Sep 2017 15:16:22 +0200 Subject: [PATCH 5/5] Further improve dragging outside of target area It is now possible to continue dragging along an edge, even if the mouse pointer is outside of the target area. This means that dragging along an edge or into a corner is now absolutely precise. --- colorpicker.js | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/colorpicker.js b/colorpicker.js index 590389a..d93c87c 100644 --- a/colorpicker.js +++ b/colorpicker.js @@ -24,20 +24,33 @@ ].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; } /** @@ -186,7 +199,7 @@ 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 }); @@ -202,7 +215,7 @@ function pickerListener(ctx, pickerElement) { return function(evt) { evt = evt || window.event; - var mouse = mousePosition(evt), + var mouse = mousePosition(evt, pickerElement, true), width = pickerElement.offsetWidth, height = pickerElement.offsetHeight; @@ -333,13 +346,13 @@ var start = function() { removeEventListener(element, 'mousedown', start); addEventListener(document.body, 'mouseup', stop); - addEventListener(element, 'mousemove', listener); + addEventListener(document.body, 'mousemove', listener); }; var stop = function() { addEventListener(element, 'mousedown', start); removeEventListener(document.body, 'mouseup', stop); - removeEventListener(element, 'mousemove', listener); + removeEventListener(document.body, 'mousemove', listener); }; addEventListener(element, 'mousedown', start);