bootstrap-colorpicker.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. /* =========================================================
  2. * bootstrap-colorpicker.js
  3. * http://www.eyecon.ro/bootstrap-colorpicker
  4. * =========================================================
  5. * Copyright 2012 Stefan Petre
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * ========================================================= */
  19. !function( $ ) {
  20. // Color object
  21. var Color = function(val) {
  22. this.value = {
  23. h: 1,
  24. s: 1,
  25. b: 1,
  26. a: 1
  27. };
  28. this.setColor(val);
  29. };
  30. Color.prototype = {
  31. constructor: Color,
  32. //parse a string to HSB
  33. setColor: function(val){
  34. val = val.toLowerCase();
  35. var that = this;
  36. $.each( CPGlobal.stringParsers, function( i, parser ) {
  37. var match = parser.re.exec( val ),
  38. values = match && parser.parse( match ),
  39. space = parser.space||'rgba';
  40. if ( values ) {
  41. if (space === 'hsla') {
  42. that.value = CPGlobal.RGBtoHSB.apply(null, CPGlobal.HSLtoRGB.apply(null, values));
  43. } else {
  44. that.value = CPGlobal.RGBtoHSB.apply(null, values);
  45. }
  46. return false;
  47. }
  48. });
  49. },
  50. setHue: function(h) {
  51. this.value.h = 1- h;
  52. },
  53. setSaturation: function(s) {
  54. this.value.s = s;
  55. },
  56. setLightness: function(b) {
  57. this.value.b = 1- b;
  58. },
  59. setAlpha: function(a) {
  60. this.value.a = parseInt((1 - a)*100, 10)/100;
  61. },
  62. // HSBtoRGB from RaphaelJS
  63. // https://github.com/DmitryBaranovskiy/raphael/
  64. toRGB: function(h, s, b, a) {
  65. if (!h) {
  66. h = this.value.h;
  67. s = this.value.s;
  68. b = this.value.b;
  69. }
  70. h *= 360;
  71. var R, G, B, X, C;
  72. h = (h % 360) / 60;
  73. C = b * s;
  74. X = C * (1 - Math.abs(h % 2 - 1));
  75. R = G = B = b - C;
  76. h = ~~h;
  77. R += [C, X, 0, 0, X, C][h];
  78. G += [X, C, C, X, 0, 0][h];
  79. B += [0, 0, X, C, C, X][h];
  80. return {
  81. r: Math.round(R*255),
  82. g: Math.round(G*255),
  83. b: Math.round(B*255),
  84. a: a||this.value.a
  85. };
  86. },
  87. toHex: function(h, s, b, a){
  88. var rgb = this.toRGB(h, s, b, a);
  89. return '#'+((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1);
  90. },
  91. toHSL: function(h, s, b, a){
  92. if (!h) {
  93. h = this.value.h;
  94. s = this.value.s;
  95. b = this.value.b;
  96. }
  97. var H = h,
  98. L = (2 - s) * b,
  99. S = s * b;
  100. if (L > 0 && L <= 1) {
  101. S /= L;
  102. } else {
  103. S /= 2 - L;
  104. }
  105. L /= 2;
  106. if (S > 1) {
  107. S = 1;
  108. }
  109. return {
  110. h: H,
  111. s: S,
  112. l: L,
  113. a: a||this.value.a
  114. };
  115. }
  116. };
  117. // Picker object
  118. var Colorpicker = function(element, options){
  119. this.element = $(element);
  120. var format = options.format||this.element.data('color-format')||'hex';
  121. this.format = CPGlobal.translateFormats[format];
  122. this.isInput = this.element.is('input');
  123. this.component = this.element.is('.color') ? this.element.find('.add-on') : false;
  124. this.picker = $(CPGlobal.template)
  125. .appendTo('body')
  126. .on('mousedown', $.proxy(this.mousedown, this));
  127. if (this.isInput) {
  128. this.element.on({
  129. 'focus': $.proxy(this.show, this),
  130. 'keyup': $.proxy(this.update, this)
  131. });
  132. } else if (this.component){
  133. this.component.on({
  134. 'click': $.proxy(this.show, this)
  135. });
  136. } else {
  137. this.element.on({
  138. 'click': $.proxy(this.show, this)
  139. });
  140. }
  141. if (format === 'rgba' || format === 'hsla') {
  142. this.picker.addClass('alpha');
  143. this.alpha = this.picker.find('.colorpicker-alpha')[0].style;
  144. }
  145. if (this.component){
  146. this.picker.find('.colorpicker-color').hide();
  147. this.preview = this.element.find('i')[0].style;
  148. } else {
  149. this.preview = this.picker.find('div:last')[0].style;
  150. }
  151. this.base = this.picker.find('div:first')[0].style;
  152. this.update();
  153. };
  154. Colorpicker.prototype = {
  155. constructor: Colorpicker,
  156. show: function(e) {
  157. this.picker.show();
  158. this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
  159. this.place();
  160. $(window).on('resize', $.proxy(this.place, this));
  161. if (!this.isInput) {
  162. if (e) {
  163. e.stopPropagation();
  164. e.preventDefault();
  165. }
  166. }
  167. $(document).on({
  168. 'mousedown': $.proxy(this.hide, this)
  169. });
  170. this.element.trigger({
  171. type: 'show',
  172. color: this.color
  173. });
  174. },
  175. update: function(){
  176. this.color = new Color(this.isInput ? this.element.prop('value') : this.element.data('color'));
  177. this.picker.find('i')
  178. .eq(0).css({left: this.color.value.s*100, top: 100 - this.color.value.b*100}).end()
  179. .eq(1).css('top', 100 * (1 - this.color.value.h)).end()
  180. .eq(2).css('top', 100 * (1 - this.color.value.a));
  181. this.previewColor();
  182. },
  183. setValue: function(newColor) {
  184. this.color = new Color(newColor);
  185. this.picker.find('i')
  186. .eq(0).css({left: this.color.value.s*100, top: 100 - this.color.value.b*100}).end()
  187. .eq(1).css('top', 100 * (1 - this.color.value.h)).end()
  188. .eq(2).css('top', 100 * (1 - this.color.value.a));
  189. this.previewColor();
  190. this.element.trigger({
  191. type: 'changeColor',
  192. color: this.color
  193. });
  194. },
  195. hide: function(){
  196. this.picker.hide();
  197. $(window).off('resize', this.place);
  198. if (!this.isInput) {
  199. $(document).off({
  200. 'mousedown': this.hide
  201. });
  202. if (this.component){
  203. this.element.find('input').prop('value', this.format.call(this));
  204. }
  205. this.element.data('color', this.format.call(this));
  206. } else {
  207. this.element.prop('value', this.format.call(this));
  208. }
  209. this.element.trigger({
  210. type: 'hide',
  211. color: this.color
  212. });
  213. },
  214. place: function(){
  215. var offset = this.component ? this.component.offset() : this.element.offset();
  216. this.picker.css({
  217. top: offset.top + this.height,
  218. left: offset.left
  219. });
  220. },
  221. //preview color change
  222. previewColor: function(){
  223. try {
  224. this.preview.backgroundColor = this.format.call(this);
  225. } catch(e) {
  226. this.preview.backgroundColor = this.color.toHex();
  227. }
  228. //set the color for brightness/saturation slider
  229. this.base.backgroundColor = this.color.toHex(this.color.value.h, 1, 1, 1);
  230. //set te color for alpha slider
  231. if (this.alpha) {
  232. this.alpha.backgroundColor = this.color.toHex();
  233. }
  234. },
  235. pointer: null,
  236. slider: null,
  237. mousedown: function(e){
  238. e.stopPropagation();
  239. e.preventDefault();
  240. var target = $(e.target);
  241. //detect the slider and set the limits and callbacks
  242. var zone = target.closest('div');
  243. if (!zone.is('.colorpicker')) {
  244. if (zone.is('.colorpicker-saturation')) {
  245. this.slider = $.extend({}, CPGlobal.sliders.saturation);
  246. }
  247. else if (zone.is('.colorpicker-hue')) {
  248. this.slider = $.extend({}, CPGlobal.sliders.hue);
  249. }
  250. else if (zone.is('.colorpicker-alpha')) {
  251. this.slider = $.extend({}, CPGlobal.sliders.alpha);
  252. } else {
  253. return false;
  254. }
  255. var offset = zone.offset();
  256. //reference to knob's style
  257. this.slider.knob = zone.find('i')[0].style;
  258. this.slider.left = e.pageX - offset.left;
  259. this.slider.top = e.pageY - offset.top;
  260. this.pointer = {
  261. left: e.pageX,
  262. top: e.pageY
  263. };
  264. //trigger mousemove to move the knob to the current position
  265. $(document).on({
  266. mousemove: $.proxy(this.mousemove, this),
  267. mouseup: $.proxy(this.mouseup, this)
  268. }).trigger('mousemove');
  269. }
  270. return false;
  271. },
  272. mousemove: function(e){
  273. e.stopPropagation();
  274. e.preventDefault();
  275. var left = Math.max(
  276. 0,
  277. Math.min(
  278. this.slider.maxLeft,
  279. this.slider.left + ((e.pageX||this.pointer.left) - this.pointer.left)
  280. )
  281. );
  282. var top = Math.max(
  283. 0,
  284. Math.min(
  285. this.slider.maxTop,
  286. this.slider.top + ((e.pageY||this.pointer.top) - this.pointer.top)
  287. )
  288. );
  289. this.slider.knob.left = left + 'px';
  290. this.slider.knob.top = top + 'px';
  291. if (this.slider.callLeft) {
  292. this.color[this.slider.callLeft].call(this.color, left/100);
  293. }
  294. if (this.slider.callTop) {
  295. this.color[this.slider.callTop].call(this.color, top/100);
  296. }
  297. this.previewColor();
  298. this.element.trigger({
  299. type: 'changeColor',
  300. color: this.color
  301. });
  302. return false;
  303. },
  304. mouseup: function(e){
  305. e.stopPropagation();
  306. e.preventDefault();
  307. $(document).off({
  308. mousemove: this.mousemove,
  309. mouseup: this.mouseup
  310. });
  311. return false;
  312. }
  313. }
  314. $.fn.colorpicker = function ( option ) {
  315. return this.each(function () {
  316. var $this = $(this),
  317. data = $this.data('colorpicker'),
  318. options = typeof option === 'object' && option;
  319. if (!data) {
  320. $this.data('colorpicker', (data = new Colorpicker(this, $.extend({}, $.fn.colorpicker.defaults,options))));
  321. }
  322. if (typeof option === 'string') data[option]();
  323. });
  324. };
  325. $.fn.colorpicker.defaults = {
  326. };
  327. $.fn.colorpicker.Constructor = Colorpicker;
  328. var CPGlobal = {
  329. // translate a format from Color object to a string
  330. translateFormats: {
  331. 'rgb': function(){
  332. var rgb = this.color.toRGB();
  333. return 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')';
  334. },
  335. 'rgba': function(){
  336. var rgb = this.color.toRGB();
  337. return 'rgba('+rgb.r+','+rgb.g+','+rgb.b+','+rgb.a+')';
  338. },
  339. 'hsl': function(){
  340. var hsl = this.color.toHSL();
  341. return 'hsl('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%)';
  342. },
  343. 'hsla': function(){
  344. var hsl = this.color.toHSL();
  345. return 'hsla('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%,'+hsl.a+')';
  346. },
  347. 'hex': function(){
  348. return this.color.toHex();
  349. }
  350. },
  351. sliders: {
  352. saturation: {
  353. maxLeft: 100,
  354. maxTop: 100,
  355. callLeft: 'setSaturation',
  356. callTop: 'setLightness'
  357. },
  358. hue: {
  359. maxLeft: 0,
  360. maxTop: 100,
  361. callLeft: false,
  362. callTop: 'setHue'
  363. },
  364. alpha: {
  365. maxLeft: 0,
  366. maxTop: 100,
  367. callLeft: false,
  368. callTop: 'setAlpha'
  369. }
  370. },
  371. // HSBtoRGB from RaphaelJS
  372. // https://github.com/DmitryBaranovskiy/raphael/
  373. RGBtoHSB: function (r, g, b, a){
  374. r /= 255;
  375. g /= 255;
  376. b /= 255;
  377. var H, S, V, C;
  378. V = Math.max(r, g, b);
  379. C = V - Math.min(r, g, b);
  380. H = (C === 0 ? null :
  381. V == r ? (g - b) / C :
  382. V == g ? (b - r) / C + 2 :
  383. (r - g) / C + 4
  384. );
  385. H = ((H + 360) % 6) * 60 / 360;
  386. S = C === 0 ? 0 : C / V;
  387. return {h: H||1, s: S, b: V, a: a||1};
  388. },
  389. HueToRGB: function (p, q, h) {
  390. if (h < 0)
  391. h += 1;
  392. else if (h > 1)
  393. h -= 1;
  394. if ((h * 6) < 1)
  395. return p + (q - p) * h * 6;
  396. else if ((h * 2) < 1)
  397. return q;
  398. else if ((h * 3) < 2)
  399. return p + (q - p) * ((2 / 3) - h) * 6;
  400. else
  401. return p;
  402. },
  403. HSLtoRGB: function (h, s, l, a)
  404. {
  405. if (s < 0) {
  406. s = 0;
  407. }
  408. var q;
  409. if (l <= 0.5) {
  410. q = l * (1 + s);
  411. } else {
  412. q = l + s - (l * s);
  413. }
  414. var p = 2 * l - q;
  415. var tr = h + (1 / 3);
  416. var tg = h;
  417. var tb = h - (1 / 3);
  418. var r = Math.round(CPGlobal.HueToRGB(p, q, tr) * 255);
  419. var g = Math.round(CPGlobal.HueToRGB(p, q, tg) * 255);
  420. var b = Math.round(CPGlobal.HueToRGB(p, q, tb) * 255);
  421. return [r, g, b, a||1];
  422. },
  423. // a set of RE's that can match strings and generate color tuples.
  424. // from John Resig color plugin
  425. // https://github.com/jquery/jquery-color/
  426. stringParsers: [
  427. {
  428. re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
  429. parse: function( execResult ) {
  430. return [
  431. execResult[ 1 ],
  432. execResult[ 2 ],
  433. execResult[ 3 ],
  434. execResult[ 4 ]
  435. ];
  436. }
  437. }, {
  438. re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
  439. parse: function( execResult ) {
  440. return [
  441. 2.55 * execResult[1],
  442. 2.55 * execResult[2],
  443. 2.55 * execResult[3],
  444. execResult[ 4 ]
  445. ];
  446. }
  447. }, {
  448. re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
  449. parse: function( execResult ) {
  450. return [
  451. parseInt( execResult[ 1 ], 16 ),
  452. parseInt( execResult[ 2 ], 16 ),
  453. parseInt( execResult[ 3 ], 16 )
  454. ];
  455. }
  456. }, {
  457. re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
  458. parse: function( execResult ) {
  459. return [
  460. parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
  461. parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
  462. parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
  463. ];
  464. }
  465. }, {
  466. re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
  467. space: 'hsla',
  468. parse: function( execResult ) {
  469. return [
  470. execResult[1]/360,
  471. execResult[2] / 100,
  472. execResult[3] / 100,
  473. execResult[4]
  474. ];
  475. }
  476. }
  477. ],
  478. template: '<div class="colorpicker dropdown-menu">'+
  479. '<div class="colorpicker-saturation"><i><b></b></i></div>'+
  480. '<div class="colorpicker-hue"><i></i></div>'+
  481. '<div class="colorpicker-alpha"><i></i></div>'+
  482. '<div class="colorpicker-color"><div /></div>'+
  483. '</div>'
  484. };
  485. }( window.jQuery )