Last active
August 29, 2015 13:56
-
-
Save clouddueling/9072760 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 'use strict'; | |
| angular.module('colorpicker.module', []) | |
| .factory('Helper', function () { | |
| return { | |
| closestSlider: function (elem) { | |
| var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector; | |
| if (matchesSelector.bind(elem)('I')) { | |
| return elem.parentNode; | |
| } | |
| return elem; | |
| }, | |
| getOffset: function (elem) { | |
| var | |
| x = 0, | |
| y = 0, | |
| scrollX = 0, | |
| scrollY = 0; | |
| while (elem && !isNaN(elem.offsetLeft) && !isNaN(elem.offsetTop)) { | |
| x += elem.offsetLeft; | |
| y += elem.offsetTop; | |
| scrollX += elem.scrollLeft; | |
| scrollY += elem.scrollTop; | |
| elem = elem.offsetParent; | |
| } | |
| return { | |
| top: y, | |
| left: x, | |
| scrollX: scrollX, | |
| scrollY: scrollY | |
| }; | |
| }, | |
| // a set of RE's that can match strings and generate color tuples. https://github.com/jquery/jquery-color/ | |
| stringParsers: [ | |
| { | |
| re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, | |
| parse: function (execResult) { | |
| return [ | |
| execResult[1], | |
| execResult[2], | |
| execResult[3], | |
| execResult[4] | |
| ]; | |
| } | |
| }, | |
| { | |
| re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, | |
| parse: function (execResult) { | |
| return [ | |
| 2.55 * execResult[1], | |
| 2.55 * execResult[2], | |
| 2.55 * execResult[3], | |
| execResult[4] | |
| ]; | |
| } | |
| }, | |
| { | |
| re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, | |
| parse: function (execResult) { | |
| return [ | |
| parseInt(execResult[1], 16), | |
| parseInt(execResult[2], 16), | |
| parseInt(execResult[3], 16) | |
| ]; | |
| } | |
| }, | |
| { | |
| re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, | |
| parse: function (execResult) { | |
| return [ | |
| parseInt(execResult[1] + execResult[1], 16), | |
| parseInt(execResult[2] + execResult[2], 16), | |
| parseInt(execResult[3] + execResult[3], 16) | |
| ]; | |
| } | |
| } | |
| ] | |
| }; | |
| }) | |
| .factory('Color', ['Helper', function (Helper) { | |
| return { | |
| value: { | |
| h: 1, | |
| s: 1, | |
| b: 1, | |
| a: 1 | |
| }, | |
| // translate a format from Color object to a string | |
| 'rgb': function () { | |
| var rgb = this.toRGB(); | |
| return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; | |
| }, | |
| 'rgba': function () { | |
| var rgb = this.toRGB(); | |
| return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')'; | |
| }, | |
| 'hex': function () { | |
| return this.toHex(); | |
| }, | |
| // HSBtoRGB from RaphaelJS | |
| RGBtoHSB: function (r, g, b, a) { | |
| r /= 255; | |
| g /= 255; | |
| b /= 255; | |
| var H, S, V, C; | |
| V = Math.max(r, g, b); | |
| C = V - Math.min(r, g, b); | |
| H = (C === 0 ? null : | |
| V == r ? (g - b) / C : | |
| V == g ? (b - r) / C + 2 : | |
| (r - g) / C + 4 | |
| ); | |
| H = ((H + 360) % 6) * 60 / 360; | |
| S = C === 0 ? 0 : C / V; | |
| return {h: H || 1, s: S, b: V, a: a || 1}; | |
| }, | |
| //parse a string to HSB | |
| setColor: function (val) { | |
| val = val.toLowerCase(); | |
| for (var key in Helper.stringParsers) { | |
| if (Helper.stringParsers.hasOwnProperty(key)) { | |
| var parser = Helper.stringParsers[key]; | |
| var match = parser.re.exec(val), | |
| values = match && parser.parse(match), | |
| space = parser.space || 'rgba'; | |
| if (values) { | |
| this.value = this.RGBtoHSB.apply(null, values); | |
| return false; | |
| } | |
| } | |
| } | |
| }, | |
| setHue: function (h) { | |
| this.value.h = 1 - h; | |
| }, | |
| setSaturation: function (s) { | |
| this.value.s = s; | |
| }, | |
| setLightness: function (b) { | |
| this.value.b = 1 - b; | |
| }, | |
| setAlpha: function (a) { | |
| this.value.a = parseInt((1 - a) * 100, 10) / 100; | |
| }, | |
| // HSBtoRGB from RaphaelJS | |
| // https://github.com/DmitryBaranovskiy/raphael/ | |
| toRGB: function (h, s, b, a) { | |
| if (!h) { | |
| h = this.value.h; | |
| s = this.value.s; | |
| b = this.value.b; | |
| } | |
| h *= 360; | |
| var R, G, B, X, C; | |
| h = (h % 360) / 60; | |
| C = b * s; | |
| X = C * (1 - Math.abs(h % 2 - 1)); | |
| R = G = B = b - C; | |
| h = ~~h; | |
| R += [C, X, 0, 0, X, C][h]; | |
| G += [X, C, C, X, 0, 0][h]; | |
| B += [0, 0, X, C, C, X][h]; | |
| return { | |
| r: Math.round(R * 255), | |
| g: Math.round(G * 255), | |
| b: Math.round(B * 255), | |
| a: a || this.value.a | |
| }; | |
| }, | |
| toHex: function (h, s, b, a) { | |
| var rgb = this.toRGB(h, s, b, a); | |
| return '#' + ((1 << 24) | (parseInt(rgb.r, 10) << 16) | (parseInt(rgb.g, 10) << 8) | parseInt(rgb.b, 10)).toString(16).substr(1); | |
| } | |
| }; | |
| }]) | |
| .factory('Slider', ['Helper', function (Helper) { | |
| var | |
| slider = { | |
| maxLeft: 0, | |
| maxTop: 0, | |
| callLeft: null, | |
| callTop: null, | |
| knob: { | |
| top: 0, | |
| left: 0 | |
| } | |
| }, | |
| pointer = {}; | |
| return { | |
| getSlider: function() { | |
| return slider; | |
| }, | |
| getLeftPosition: function(event) { | |
| return Math.max(0, Math.min(slider.maxLeft, slider.left + ((event.pageX || pointer.left) - pointer.left))); | |
| }, | |
| getTopPosition: function(event) { | |
| return Math.max(0, Math.min(slider.maxTop, slider.top + ((event.pageY || pointer.top) - pointer.top))); | |
| }, | |
| setSlider: function (event) { | |
| var | |
| target = Helper.closestSlider(event.target), | |
| targetOffset = Helper.getOffset(target); | |
| slider.knob = target.children[0].style; | |
| slider.left = event.pageX - targetOffset.left - window.pageXOffset + targetOffset.scrollX; | |
| slider.top = event.pageY - targetOffset.top - window.pageYOffset + targetOffset.scrollY; | |
| pointer = { | |
| left: event.pageX, | |
| top: event.pageY | |
| }; | |
| }, | |
| setSaturation: function(event) { | |
| slider = { | |
| maxLeft: 100, | |
| maxTop: 100, | |
| callLeft: 'setSaturation', | |
| callTop: 'setLightness' | |
| }; | |
| this.setSlider(event) | |
| }, | |
| setHue: function(event) { | |
| slider = { | |
| maxLeft: 0, | |
| maxTop: 100, | |
| callLeft: false, | |
| callTop: 'setHue' | |
| }; | |
| this.setSlider(event) | |
| }, | |
| setAlpha: function(event) { | |
| slider = { | |
| maxLeft: 0, | |
| maxTop: 100, | |
| callLeft: false, | |
| callTop: 'setAlpha' | |
| }; | |
| this.setSlider(event) | |
| }, | |
| setKnob: function(top, left) { | |
| slider.knob.top = top + 'px'; | |
| slider.knob.left = left + 'px'; | |
| } | |
| }; | |
| }]) | |
| .directive('colorpicker', ['$document', '$compile', 'Color', 'Slider', 'Helper', function ($document, $compile, Color, Slider, Helper) { | |
| return { | |
| require: '?ngModel', | |
| restrict: 'A', | |
| link: function ($scope, elem, attrs, ngModel) { | |
| var | |
| thisFormat = attrs.colorpicker ? attrs.colorpicker : 'hex', | |
| position = angular.isDefined(attrs.colorpickerPosition) ? attrs.colorpickerPosition : 'bottom', | |
| fixedPosition = angular.isDefined(attrs.colorpickerFixedPosition) ? attrs.colorpickerFixedPosition : false, | |
| target = angular.isDefined(attrs.colorpickerParent) ? elem.parent() : angular.element(document.body), | |
| withInput = angular.isDefined(attrs.colorpickerWithInput) ? attrs.colorpickerWithInput : false, | |
| inputTemplate = withInput ? '<input type="text" name="colorpicker-input">' : '', | |
| template = angular.isDefined(attrs.colorpickerTemplate) ? attrs.colorpickerTemplate : 'default', | |
| templates = { | |
| default: '<div class="colorpicker dropdown">' + | |
| '<div class="dropdown-menu">' + | |
| '<colorpicker-saturation><i></i></colorpicker-saturation>' + | |
| '<colorpicker-hue><i></i></colorpicker-hue>' + | |
| '<colorpicker-alpha><i></i></colorpicker-alpha>' + | |
| '<colorpicker-preview></colorpicker-preview>' + | |
| inputTemplate + | |
| '<button class="close close-colorpicker">×</button>' + | |
| '</div>' + | |
| '</div>', | |
| inline: '<div class="colorpicker colorpicker-inline colorpicker-visible">' + | |
| '<div class="dropdown-menu">' + | |
| '<colorpicker-saturation><i></i></colorpicker-saturation>' + | |
| '<colorpicker-hue><i></i></colorpicker-hue>' + | |
| '<colorpicker-alpha><i></i></colorpicker-alpha>' + | |
| '<colorpicker-preview></colorpicker-preview>' + | |
| inputTemplate + | |
| '</div>' + | |
| '</div>' | |
| }, | |
| colorpickerTemplate = angular.element(templates[template]), | |
| pickerColor = Color, | |
| sliderAlpha, | |
| sliderHue = colorpickerTemplate.find('colorpicker-hue'), | |
| sliderSaturation = colorpickerTemplate.find('colorpicker-saturation'), | |
| colorpickerPreview = colorpickerTemplate.find('colorpicker-preview'), | |
| pickerColorPointers = colorpickerTemplate.find('i'); | |
| $compile(colorpickerTemplate)($scope); | |
| if (withInput) { | |
| var pickerColorInput = colorpickerTemplate.find('input'); | |
| pickerColorInput | |
| .on('mousedown', function() { | |
| event.stopPropagation(); | |
| }) | |
| .on('keyup', function(event) { | |
| var newColor = this.value; | |
| elem.val(newColor); | |
| if(ngModel) { | |
| $scope.$apply(ngModel.$setViewValue(newColor)); | |
| } | |
| event.stopPropagation(); | |
| event.preventDefault(); | |
| }); | |
| elem.on('keyup', function() { | |
| pickerColorInput.val(elem.val()); | |
| }); | |
| } | |
| var bindMouseEvents = function() { | |
| $document.on('mousemove', mousemove); | |
| $document.on('mouseup', mouseup); | |
| }; | |
| if (thisFormat === 'rgba') { | |
| colorpickerTemplate.addClass('alpha'); | |
| sliderAlpha = colorpickerTemplate.find('colorpicker-alpha'); | |
| sliderAlpha | |
| .on('click', function(event) { | |
| Slider.setAlpha(event); | |
| mousemove(event); | |
| }) | |
| .on('mousedown', function(event) { | |
| Slider.setAlpha(event); | |
| bindMouseEvents(); | |
| }); | |
| } | |
| sliderHue | |
| .on('click', function(event) { | |
| Slider.setHue(event); | |
| mousemove(event); | |
| }) | |
| .on('mousedown', function(event) { | |
| Slider.setHue(event); | |
| bindMouseEvents(); | |
| }); | |
| sliderSaturation | |
| .on('click', function(event) { | |
| Slider.setSaturation(event); | |
| mousemove(event); | |
| }) | |
| .on('mousedown', function(event) { | |
| Slider.setSaturation(event); | |
| bindMouseEvents(); | |
| }); | |
| if (fixedPosition) { | |
| colorpickerTemplate.addClass('colorpicker-fixed-position'); | |
| } | |
| colorpickerTemplate.addClass('colorpicker-position-' + position); | |
| target.append(colorpickerTemplate); | |
| if(ngModel) { | |
| ngModel.$render = function () { | |
| elem.val(ngModel.$viewValue); | |
| }; | |
| $scope.$watch(attrs.ngModel, function() { | |
| update(); | |
| }); | |
| } | |
| elem.on('$destroy', function() { | |
| colorpickerTemplate.remove(); | |
| }); | |
| var previewColor = function () { | |
| try { | |
| colorpickerPreview.css('backgroundColor', pickerColor[thisFormat]()); | |
| } catch (e) { | |
| colorpickerPreview.css('backgroundColor', pickerColor.toHex()); | |
| } | |
| sliderSaturation.css('backgroundColor', pickerColor.toHex(pickerColor.value.h, 1, 1, 1)); | |
| if (thisFormat === 'rgba') { | |
| sliderAlpha.css.backgroundColor = pickerColor.toHex(); | |
| } | |
| }; | |
| var mousemove = function (event) { | |
| var | |
| left = Slider.getLeftPosition(event), | |
| top = Slider.getTopPosition(event), | |
| slider = Slider.getSlider(); | |
| Slider.setKnob(top, left); | |
| if (slider.callLeft) { | |
| pickerColor[slider.callLeft].call(pickerColor, left / 100); | |
| } | |
| if (slider.callTop) { | |
| pickerColor[slider.callTop].call(pickerColor, top / 100); | |
| } | |
| previewColor(); | |
| var newColor = pickerColor[thisFormat](); | |
| elem.val(newColor); | |
| if(ngModel) { | |
| $scope.$apply(ngModel.$setViewValue(newColor)); | |
| } | |
| if (withInput) { | |
| pickerColorInput.val(newColor); | |
| } | |
| return false; | |
| }; | |
| var mouseup = function () { | |
| $document.off('mousemove', mousemove); | |
| $document.off('mouseup', mouseup); | |
| }; | |
| var update = function () { | |
| if (withInput) { | |
| pickerColorInput.val(elem.val()); | |
| } | |
| pickerColor.setColor(elem.val()); | |
| pickerColorPointers.eq(0).css({ | |
| left: pickerColor.value.s * 100 + 'px', | |
| top: 100 - pickerColor.value.b * 100 + 'px' | |
| }); | |
| pickerColorPointers.eq(1).css('top', 100 * (1 - pickerColor.value.h) + 'px'); | |
| pickerColorPointers.eq(2).css('top', 100 * (1 - pickerColor.value.a) + 'px'); | |
| previewColor(); | |
| }; | |
| var getColorpickerTemplatePosition = function() { | |
| var | |
| positionValue, | |
| positionOffset = Helper.getOffset(elem[0]); | |
| if(angular.isDefined(attrs.colorpickerParent)) { | |
| positionOffset.left = 0; | |
| positionOffset.top = 0; | |
| } | |
| if (position === 'top') { | |
| positionValue = { | |
| 'top': positionOffset.top - 147, | |
| 'left': positionOffset.left | |
| }; | |
| } else if (position === 'right') { | |
| positionValue = { | |
| 'top': positionOffset.top, | |
| 'left': positionOffset.left + 126 | |
| }; | |
| } else if (position === 'bottom') { | |
| positionValue = { | |
| 'top': positionOffset.top + elem[0].offsetHeight + 2, | |
| 'left': positionOffset.left | |
| }; | |
| } else if (position === 'left') { | |
| positionValue = { | |
| 'top': positionOffset.top, | |
| 'left': positionOffset.left - 150 | |
| }; | |
| } | |
| return { | |
| 'top': positionValue.top + 'px', | |
| 'left': positionValue.left + 'px' | |
| }; | |
| }; | |
| elem.on('click', function () { | |
| update(); | |
| if (template !== 'inline') { | |
| colorpickerTemplate | |
| .addClass('colorpicker-visible') | |
| .css(getColorpickerTemplatePosition()); | |
| } | |
| }); | |
| colorpickerTemplate.on('mousedown', function (event) { | |
| event.stopPropagation(); | |
| event.preventDefault(); | |
| }); | |
| var hideColorpickerTemplate = function() { | |
| if (colorpickerTemplate.hasClass('colorpicker-visible')) { | |
| colorpickerTemplate.removeClass('colorpicker-visible'); | |
| } | |
| }; | |
| colorpickerTemplate.find('button').on('click', function () { | |
| hideColorpickerTemplate(); | |
| }); | |
| $document.on('mousedown', function () { | |
| if (template !== 'inline') { | |
| hideColorpickerTemplate(); | |
| } | |
| }); | |
| update(); | |
| } | |
| }; | |
| }]); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .colorpicker-visible, | |
| .colorpicker-visible .dropdown-menu { | |
| display: block !important; | |
| } | |
| colorpicker-saturation { | |
| display: block; | |
| width: 100px; | |
| height: 100px; | |
| background-image: data-uri('../img/saturation.png'); | |
| cursor: crosshair; | |
| float: left; | |
| i { | |
| display: block; | |
| height: 7px; | |
| width: 7px; | |
| border: 1px solid #000; | |
| border-radius: 5px; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| margin: -4px 0 0 -4px; | |
| &::after { | |
| content: ''; | |
| display: block; | |
| height: 7px; | |
| width: 7px; | |
| border: 1px solid #fff; | |
| border-radius: 5px; | |
| } | |
| } | |
| } | |
| colorpicker-hue, | |
| colorpicker-alpha { | |
| width: 15px; | |
| height: 100px; | |
| float: left; | |
| cursor: row-resize; | |
| margin-left: 4px; | |
| margin-bottom: 4px; | |
| i { | |
| display: block; | |
| height: 2px; | |
| background: #000; | |
| border-top: 1px solid #fff; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| margin-top: -1px; | |
| } | |
| } | |
| colorpicker-hue { | |
| background-image: data-uri('../img/hue.png'); | |
| } | |
| colorpicker-alpha { | |
| display: none; | |
| } | |
| colorpicker-alpha, | |
| .colorpicker-color { | |
| background-image: data-uri('../img/alpha.png'); | |
| } | |
| .colorpicker { | |
| top: 0; | |
| left: 0; | |
| position: absolute; | |
| z-index: 9999; | |
| display: none; | |
| colorpicker-hue, | |
| colorpicker-alpha, | |
| colorpicker-saturation { | |
| position: relative; | |
| } | |
| input { | |
| width: 100px; | |
| font-size: 11px; | |
| color: #000; | |
| background-color: #fff; | |
| } | |
| &.alpha { | |
| min-width: 140px; | |
| colorpicker-alpha { | |
| display: block; | |
| } | |
| } | |
| &.colorpicker-fixed-position { | |
| position: fixed; | |
| } | |
| .dropdown-menu { | |
| &::after, | |
| &::before { | |
| content: ''; | |
| display: inline-block; | |
| position: absolute; | |
| } | |
| &::after { | |
| clear: both; | |
| border: 6px solid transparent; | |
| top: -5px; | |
| left: 7px; | |
| } | |
| &::before { | |
| border: 7px solid transparent; | |
| top: -6px; | |
| left: 6px; | |
| } | |
| } | |
| .dropdown-menu { | |
| position: static; | |
| top: 0; | |
| left: 0; | |
| min-width: 120px; | |
| padding: 4px; | |
| margin-top: 0; | |
| } | |
| } | |
| .colorpicker-position-top { | |
| .dropdown-menu { | |
| &::after { | |
| border-top: 6px solid #fff; | |
| border-bottom: 0; | |
| top: auto; | |
| bottom: -5px; | |
| } | |
| &::before { | |
| border-top: 7px solid rgba(0, 0, 0, 0.2); | |
| border-bottom: 0; | |
| top: auto; | |
| bottom: -6px; | |
| } | |
| } | |
| } | |
| .colorpicker-position-right { | |
| .dropdown-menu { | |
| &::after { | |
| border-right: 6px solid #fff; | |
| border-left: 0; | |
| top: 11px; | |
| left: -5px; | |
| } | |
| &::before { | |
| border-right: 7px solid rgba(0, 0, 0, 0.2); | |
| border-left: 0; | |
| top: 10px; | |
| left: -6px; | |
| } | |
| } | |
| } | |
| .colorpicker-position-bottom { | |
| .dropdown-menu { | |
| &::after { | |
| border-bottom: 6px solid #fff; | |
| border-top: 0; | |
| } | |
| &::before { | |
| border-bottom: 7px solid rgba(0, 0, 0, 0.2); | |
| border-top: 0; | |
| } | |
| } | |
| } | |
| .colorpicker-position-left { | |
| .dropdown-menu { | |
| &::after { | |
| border-left: 6px solid #fff; | |
| border-right: 0; | |
| top: 11px; | |
| left: auto; | |
| right: -5px; | |
| } | |
| &::before { | |
| border-left: 7px solid rgba(0, 0, 0, 0.2); | |
| border-right: 0; | |
| top: 10px; | |
| left: auto; | |
| right: -6px; | |
| } | |
| } | |
| } | |
| colorpicker-preview { | |
| display: block; | |
| height: 10px; | |
| margin: 5px 0 3px 0; | |
| clear: both; | |
| background-position: 0 100%; | |
| } | |
| .colorpicker-inline { | |
| position: relative; | |
| .dropdown-menu:after { | |
| border: none !important; | |
| content: "" !important; | |
| } | |
| .dropdown-menu:before { | |
| border: none !important; | |
| content: "" !important; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment