diff --git a/colorpicker.js b/colorpicker.js index e13892c..d93c87c 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,24 +20,37 @@ '
', '
', '
' - + ].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; } /** @@ -122,7 +135,7 @@ '', '' ].join(''); - + if (!document.namespaces['v']) document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); } @@ -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; @@ -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; @@ -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 }); @@ -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; @@ -214,7 +227,7 @@ }; var uniqID = 0; - + /** * ColorPicker. * @param {DOMElement} slideElement HSV slide element. @@ -222,7 +235,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 +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; @@ -263,20 +276,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 +297,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,15 +314,27 @@ 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. @@ -317,19 +342,29 @@ * @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; } }); + } @@ -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) }; }; @@ -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; }; @@ -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: , g: , b: }. @@ -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'; - } + } }; /**