colorpicker.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. /*
  2. * jQuery MiniColors: A tiny color picker built on jQuery
  3. *
  4. * Copyright Cory LaViska for A Beautiful Site, LLC. (http://www.abeautifulsite.net/)
  5. *
  6. * Licensed under the MIT license: http://opensource.org/licenses/MIT
  7. *
  8. */
  9. if(jQuery) (function($) {
  10. // Defaults
  11. $.minicolors = {
  12. defaults: {
  13. animationSpeed: 50,
  14. animationEasing: 'swing',
  15. change: null,
  16. changeDelay: 0,
  17. control: 'hue',
  18. defaultValue: '',
  19. hide: null,
  20. hideSpeed: 100,
  21. inline: false,
  22. letterCase: 'lowercase',
  23. opacity: false,
  24. position: 'bottom left',
  25. show: null,
  26. showSpeed: 100,
  27. theme: 'default'
  28. }
  29. };
  30. // Public methods
  31. $.extend($.fn, {
  32. minicolors: function(method, data) {
  33. switch(method) {
  34. // Destroy the control
  35. case 'destroy':
  36. $(this).each( function() {
  37. destroy($(this));
  38. });
  39. return $(this);
  40. // Hide the color picker
  41. case 'hide':
  42. hide();
  43. return $(this);
  44. // Get/set opacity
  45. case 'opacity':
  46. // Getter
  47. if( data === undefined ) {
  48. // Getter
  49. return $(this).attr('data-opacity');
  50. } else {
  51. // Setter
  52. $(this).each( function() {
  53. updateFromInput($(this).attr('data-opacity', data));
  54. });
  55. }
  56. return $(this);
  57. // Get an RGB(A) object based on the current color/opacity
  58. case 'rgbObject':
  59. return rgbObject($(this), method === 'rgbaObject');
  60. // Get an RGB(A) string based on the current color/opacity
  61. case 'rgbString':
  62. case 'rgbaString':
  63. return rgbString($(this), method === 'rgbaString');
  64. // Get/set settings on the fly
  65. case 'settings':
  66. if( data === undefined ) {
  67. return $(this).data('minicolors-settings');
  68. } else {
  69. // Setter
  70. $(this).each( function() {
  71. var settings = $(this).data('minicolors-settings') || {};
  72. destroy($(this));
  73. $(this).minicolors($.extend(true, settings, data));
  74. });
  75. }
  76. return $(this);
  77. // Show the color picker
  78. case 'show':
  79. show( $(this).eq(0) );
  80. return $(this);
  81. // Get/set the hex color value
  82. case 'value':
  83. if( data === undefined ) {
  84. // Getter
  85. return $(this).val();
  86. } else {
  87. // Setter
  88. $(this).each( function() {
  89. updateFromInput($(this).val(data));
  90. });
  91. }
  92. return $(this);
  93. // Initializes the control
  94. default:
  95. if( method !== 'create' ) data = method;
  96. $(this).each( function() {
  97. init($(this), data);
  98. });
  99. return $(this);
  100. }
  101. }
  102. });
  103. // Initialize input elements
  104. function init(input, settings) {
  105. var minicolors = $('<div class="minicolors" />'),
  106. defaults = $.minicolors.defaults;
  107. // Do nothing if already initialized
  108. if( input.data('minicolors-initialized') ) return;
  109. // Handle settings
  110. settings = $.extend(true, {}, defaults, settings);
  111. // The wrapper
  112. minicolors
  113. .addClass('minicolors-theme-' + settings.theme)
  114. .toggleClass('minicolors-with-opacity', settings.opacity);
  115. // Custom positioning
  116. if( settings.position !== undefined ) {
  117. $.each(settings.position.split(' '), function() {
  118. minicolors.addClass('minicolors-position-' + this);
  119. });
  120. }
  121. // The input
  122. input
  123. .addClass('minicolors-input')
  124. .data('minicolors-initialized', false)
  125. .data('minicolors-settings', settings)
  126. .prop('size', 7)
  127. .wrap(minicolors)
  128. .after(
  129. '<div class="minicolors-panel minicolors-slider-' + settings.control + '">' +
  130. '<div class="minicolors-slider">' +
  131. '<div class="minicolors-picker"></div>' +
  132. '</div>' +
  133. '<div class="minicolors-opacity-slider">' +
  134. '<div class="minicolors-picker"></div>' +
  135. '</div>' +
  136. '<div class="minicolors-grid">' +
  137. '<div class="minicolors-grid-inner"></div>' +
  138. '<div class="minicolors-picker"><div></div></div>' +
  139. '</div>' +
  140. '</div>'
  141. );
  142. // The swatch
  143. if( !settings.inline ) {
  144. input.after('<span class="minicolors-swatch"><span class="minicolors-swatch-color"></span></span>');
  145. input.next('.minicolors-swatch').on('click', function(event) {
  146. event.preventDefault();
  147. input.focus();
  148. });
  149. }
  150. // Prevent text selection in IE
  151. input.parent().find('.minicolors-panel').on('selectstart', function() { return false; }).end();
  152. // Inline controls
  153. if( settings.inline ) input.parent().addClass('minicolors-inline');
  154. updateFromInput(input, false);
  155. input.data('minicolors-initialized', true);
  156. }
  157. // Returns the input back to its original state
  158. function destroy(input) {
  159. var minicolors = input.parent();
  160. // Revert the input element
  161. input
  162. .removeData('minicolors-initialized')
  163. .removeData('minicolors-settings')
  164. .removeProp('size')
  165. .removeClass('minicolors-input');
  166. // Remove the wrap and destroy whatever remains
  167. minicolors.before(input).remove();
  168. }
  169. // Shows the specified dropdown panel
  170. function show(input) {
  171. var minicolors = input.parent(),
  172. panel = minicolors.find('.minicolors-panel'),
  173. settings = input.data('minicolors-settings');
  174. // Do nothing if uninitialized, disabled, inline, or already open
  175. if( !input.data('minicolors-initialized') ||
  176. input.prop('disabled') ||
  177. minicolors.hasClass('minicolors-inline') ||
  178. minicolors.hasClass('minicolors-focus')
  179. ) return;
  180. hide();
  181. minicolors.addClass('minicolors-focus');
  182. panel
  183. .stop(true, true)
  184. .fadeIn(settings.showSpeed, function() {
  185. if( settings.show ) settings.show.call(input.get(0));
  186. });
  187. }
  188. // Hides all dropdown panels
  189. function hide() {
  190. $('.minicolors-focus').each( function() {
  191. var minicolors = $(this),
  192. input = minicolors.find('.minicolors-input'),
  193. panel = minicolors.find('.minicolors-panel'),
  194. settings = input.data('minicolors-settings');
  195. panel.fadeOut(settings.hideSpeed, function() {
  196. if( settings.hide ) settings.hide.call(input.get(0));
  197. minicolors.removeClass('minicolors-focus');
  198. });
  199. });
  200. }
  201. // Moves the selected picker
  202. function move(target, event, animate) {
  203. var input = target.parents('.minicolors').find('.minicolors-input'),
  204. settings = input.data('minicolors-settings'),
  205. picker = target.find('[class$=-picker]'),
  206. offsetX = target.offset().left,
  207. offsetY = target.offset().top,
  208. x = Math.round(event.pageX - offsetX),
  209. y = Math.round(event.pageY - offsetY),
  210. duration = animate ? settings.animationSpeed : 0,
  211. wx, wy, r, phi;
  212. // Touch support
  213. if( event.originalEvent.changedTouches ) {
  214. x = event.originalEvent.changedTouches[0].pageX - offsetX;
  215. y = event.originalEvent.changedTouches[0].pageY - offsetY;
  216. }
  217. // Constrain picker to its container
  218. if( x < 0 ) x = 0;
  219. if( y < 0 ) y = 0;
  220. if( x > target.width() ) x = target.width();
  221. if( y > target.height() ) y = target.height();
  222. // Constrain color wheel values to the wheel
  223. if( target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid') ) {
  224. wx = 75 - x;
  225. wy = 75 - y;
  226. r = Math.sqrt(wx * wx + wy * wy);
  227. phi = Math.atan2(wy, wx);
  228. if( phi < 0 ) phi += Math.PI * 2;
  229. if( r > 75 ) {
  230. r = 75;
  231. x = 75 - (75 * Math.cos(phi));
  232. y = 75 - (75 * Math.sin(phi));
  233. }
  234. x = Math.round(x);
  235. y = Math.round(y);
  236. }
  237. // Move the picker
  238. if( target.is('.minicolors-grid') ) {
  239. picker
  240. .stop(true)
  241. .animate({
  242. top: y + 'px',
  243. left: x + 'px'
  244. }, duration, settings.animationEasing, function() {
  245. updateFromControl(input, target);
  246. });
  247. } else {
  248. picker
  249. .stop(true)
  250. .animate({
  251. top: y + 'px'
  252. }, duration, settings.animationEasing, function() {
  253. updateFromControl(input, target);
  254. });
  255. }
  256. }
  257. // Sets the input based on the color picker values
  258. function updateFromControl(input, target) {
  259. function getCoords(picker, container) {
  260. var left, top;
  261. if( !picker.length || !container ) return null;
  262. left = picker.offset().left;
  263. top = picker.offset().top;
  264. return {
  265. x: left - container.offset().left + (picker.outerWidth() / 2),
  266. y: top - container.offset().top + (picker.outerHeight() / 2)
  267. };
  268. }
  269. var hue, saturation, brightness, x, y, r, phi,
  270. hex = input.val(),
  271. opacity = input.attr('data-opacity'),
  272. // Helpful references
  273. minicolors = input.parent(),
  274. settings = input.data('minicolors-settings'),
  275. swatch = minicolors.find('.minicolors-swatch'),
  276. // Panel objects
  277. grid = minicolors.find('.minicolors-grid'),
  278. slider = minicolors.find('.minicolors-slider'),
  279. opacitySlider = minicolors.find('.minicolors-opacity-slider'),
  280. // Picker objects
  281. gridPicker = grid.find('[class$=-picker]'),
  282. sliderPicker = slider.find('[class$=-picker]'),
  283. opacityPicker = opacitySlider.find('[class$=-picker]'),
  284. // Picker positions
  285. gridPos = getCoords(gridPicker, grid),
  286. sliderPos = getCoords(sliderPicker, slider),
  287. opacityPos = getCoords(opacityPicker, opacitySlider);
  288. // Handle colors
  289. if( target.is('.minicolors-grid, .minicolors-slider') ) {
  290. // Determine HSB values
  291. switch(settings.control) {
  292. case 'wheel':
  293. // Calculate hue, saturation, and brightness
  294. x = (grid.width() / 2) - gridPos.x;
  295. y = (grid.height() / 2) - gridPos.y;
  296. r = Math.sqrt(x * x + y * y);
  297. phi = Math.atan2(y, x);
  298. if( phi < 0 ) phi += Math.PI * 2;
  299. if( r > 75 ) {
  300. r = 75;
  301. gridPos.x = 69 - (75 * Math.cos(phi));
  302. gridPos.y = 69 - (75 * Math.sin(phi));
  303. }
  304. saturation = keepWithin(r / 0.75, 0, 100);
  305. hue = keepWithin(phi * 180 / Math.PI, 0, 360);
  306. brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
  307. hex = hsb2hex({
  308. h: hue,
  309. s: saturation,
  310. b: brightness
  311. });
  312. // Update UI
  313. slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
  314. break;
  315. case 'saturation':
  316. // Calculate hue, saturation, and brightness
  317. hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
  318. saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
  319. brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
  320. hex = hsb2hex({
  321. h: hue,
  322. s: saturation,
  323. b: brightness
  324. });
  325. // Update UI
  326. slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness }));
  327. minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100);
  328. break;
  329. case 'brightness':
  330. // Calculate hue, saturation, and brightness
  331. hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
  332. saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
  333. brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
  334. hex = hsb2hex({
  335. h: hue,
  336. s: saturation,
  337. b: brightness
  338. });
  339. // Update UI
  340. slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
  341. minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100));
  342. break;
  343. default:
  344. // Calculate hue, saturation, and brightness
  345. hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360);
  346. saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100);
  347. brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
  348. hex = hsb2hex({
  349. h: hue,
  350. s: saturation,
  351. b: brightness
  352. });
  353. // Update UI
  354. grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 }));
  355. break;
  356. }
  357. // Adjust case
  358. input.val( convertCase(hex, settings.letterCase) );
  359. }
  360. // Handle opacity
  361. if( target.is('.minicolors-opacity-slider') ) {
  362. if( settings.opacity ) {
  363. opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2);
  364. } else {
  365. opacity = 1;
  366. }
  367. if( settings.opacity ) input.attr('data-opacity', opacity);
  368. }
  369. // Set swatch color
  370. swatch.find('SPAN').css({
  371. backgroundColor: hex,
  372. opacity: opacity
  373. });
  374. // Handle change event
  375. doChange(input, hex, opacity);
  376. }
  377. // Sets the color picker values from the input
  378. function updateFromInput(input, preserveInputValue) {
  379. var hex,
  380. hsb,
  381. opacity,
  382. x, y, r, phi,
  383. // Helpful references
  384. minicolors = input.parent(),
  385. settings = input.data('minicolors-settings'),
  386. swatch = minicolors.find('.minicolors-swatch'),
  387. // Panel objects
  388. grid = minicolors.find('.minicolors-grid'),
  389. slider = minicolors.find('.minicolors-slider'),
  390. opacitySlider = minicolors.find('.minicolors-opacity-slider'),
  391. // Picker objects
  392. gridPicker = grid.find('[class$=-picker]'),
  393. sliderPicker = slider.find('[class$=-picker]'),
  394. opacityPicker = opacitySlider.find('[class$=-picker]');
  395. // Determine hex/HSB values
  396. hex = convertCase(parseHex(input.val(), true), settings.letterCase);
  397. if( !hex ){
  398. hex = convertCase(parseHex(settings.defaultValue, true), settings.letterCase);
  399. }
  400. hsb = hex2hsb(hex);
  401. // Update input value
  402. if( !preserveInputValue ) input.val(hex);
  403. // Determine opacity value
  404. if( settings.opacity ) {
  405. // Get from data-opacity attribute and keep within 0-1 range
  406. opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1);
  407. if( isNaN(opacity) ) opacity = 1;
  408. input.attr('data-opacity', opacity);
  409. swatch.find('SPAN').css('opacity', opacity);
  410. // Set opacity picker position
  411. y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height());
  412. opacityPicker.css('top', y + 'px');
  413. }
  414. // Update swatch
  415. swatch.find('SPAN').css('backgroundColor', hex);
  416. // Determine picker locations
  417. switch(settings.control) {
  418. case 'wheel':
  419. // Set grid position
  420. r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2);
  421. phi = hsb.h * Math.PI / 180;
  422. x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width());
  423. y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height());
  424. gridPicker.css({
  425. top: y + 'px',
  426. left: x + 'px'
  427. });
  428. // Set slider position
  429. y = 150 - (hsb.b / (100 / grid.height()));
  430. if( hex === '' ) y = 0;
  431. sliderPicker.css('top', y + 'px');
  432. // Update panel color
  433. slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
  434. break;
  435. case 'saturation':
  436. // Set grid position
  437. x = keepWithin((5 * hsb.h) / 12, 0, 150);
  438. y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
  439. gridPicker.css({
  440. top: y + 'px',
  441. left: x + 'px'
  442. });
  443. // Set slider position
  444. y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height());
  445. sliderPicker.css('top', y + 'px');
  446. // Update UI
  447. slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b }));
  448. minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100);
  449. break;
  450. case 'brightness':
  451. // Set grid position
  452. x = keepWithin((5 * hsb.h) / 12, 0, 150);
  453. y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height());
  454. gridPicker.css({
  455. top: y + 'px',
  456. left: x + 'px'
  457. });
  458. // Set slider position
  459. y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height());
  460. sliderPicker.css('top', y + 'px');
  461. // Update UI
  462. slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
  463. minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100));
  464. break;
  465. default:
  466. // Set grid position
  467. x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width());
  468. y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
  469. gridPicker.css({
  470. top: y + 'px',
  471. left: x + 'px'
  472. });
  473. // Set slider position
  474. y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height());
  475. sliderPicker.css('top', y + 'px');
  476. // Update panel color
  477. grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 }));
  478. break;
  479. }
  480. // Fire change event, but only if minicolors is fully initialized
  481. if( input.data('minicolors-initialized') ) {
  482. doChange(input, hex, opacity);
  483. }
  484. }
  485. // Runs the change and changeDelay callbacks
  486. function doChange(input, hex, opacity) {
  487. var settings = input.data('minicolors-settings'),
  488. lastChange = input.data('minicolors-lastChange');
  489. // Only run if it actually changed
  490. if( !lastChange || lastChange.hex !== hex || lastChange.opacity !== opacity ) {
  491. // Remember last-changed value
  492. input.data('minicolors-lastChange', {
  493. hex: hex,
  494. opacity: opacity
  495. });
  496. // Fire change event
  497. if( settings.change ) {
  498. if( settings.changeDelay ) {
  499. // Call after a delay
  500. clearTimeout(input.data('minicolors-changeTimeout'));
  501. input.data('minicolors-changeTimeout', setTimeout( function() {
  502. settings.change.call(input.get(0), hex, opacity);
  503. }, settings.changeDelay));
  504. } else {
  505. // Call immediately
  506. settings.change.call(input.get(0), hex, opacity);
  507. }
  508. }
  509. input.trigger('change').trigger('input');
  510. }
  511. }
  512. // Generates an RGB(A) object based on the input's value
  513. function rgbObject(input) {
  514. var hex = parseHex($(input).val(), true),
  515. rgb = hex2rgb(hex),
  516. opacity = $(input).attr('data-opacity');
  517. if( !rgb ) return null;
  518. if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) });
  519. return rgb;
  520. }
  521. // Genearates an RGB(A) string based on the input's value
  522. function rgbString(input, alpha) {
  523. var hex = parseHex($(input).val(), true),
  524. rgb = hex2rgb(hex),
  525. opacity = $(input).attr('data-opacity');
  526. if( !rgb ) return null;
  527. if( opacity === undefined ) opacity = 1;
  528. if( alpha ) {
  529. return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')';
  530. } else {
  531. return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
  532. }
  533. }
  534. // Converts to the letter case specified in settings
  535. function convertCase(string, letterCase) {
  536. return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase();
  537. }
  538. // Parses a string and returns a valid hex string when possible
  539. function parseHex(string, expand) {
  540. string = string.replace(/[^A-F0-9]/ig, '');
  541. if( string.length !== 3 && string.length !== 6 ) return '';
  542. if( string.length === 3 && expand ) {
  543. string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
  544. }
  545. return '#' + string;
  546. }
  547. // Keeps value within min and max
  548. function keepWithin(value, min, max) {
  549. if( value < min ) value = min;
  550. if( value > max ) value = max;
  551. return value;
  552. }
  553. // Converts an HSB object to an RGB object
  554. function hsb2rgb(hsb) {
  555. var rgb = {};
  556. var h = Math.round(hsb.h);
  557. var s = Math.round(hsb.s * 255 / 100);
  558. var v = Math.round(hsb.b * 255 / 100);
  559. if(s === 0) {
  560. rgb.r = rgb.g = rgb.b = v;
  561. } else {
  562. var t1 = v;
  563. var t2 = (255 - s) * v / 255;
  564. var t3 = (t1 - t2) * (h % 60) / 60;
  565. if( h === 360 ) h = 0;
  566. if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; }
  567. else if( h < 120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; }
  568. else if( h < 180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; }
  569. else if( h < 240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; }
  570. else if( h < 300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; }
  571. else if( h < 360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; }
  572. else { rgb.r = 0; rgb.g = 0; rgb.b = 0; }
  573. }
  574. return {
  575. r: Math.round(rgb.r),
  576. g: Math.round(rgb.g),
  577. b: Math.round(rgb.b)
  578. };
  579. }
  580. // Converts an RGB object to a hex string
  581. function rgb2hex(rgb) {
  582. var hex = [
  583. rgb.r.toString(16),
  584. rgb.g.toString(16),
  585. rgb.b.toString(16)
  586. ];
  587. $.each(hex, function(nr, val) {
  588. if (val.length === 1) hex[nr] = '0' + val;
  589. });
  590. return '#' + hex.join('');
  591. }
  592. // Converts an HSB object to a hex string
  593. function hsb2hex(hsb) {
  594. return rgb2hex(hsb2rgb(hsb));
  595. }
  596. // Converts a hex string to an HSB object
  597. function hex2hsb(hex) {
  598. var hsb = rgb2hsb(hex2rgb(hex));
  599. if( hsb.s === 0 ) hsb.h = 360;
  600. return hsb;
  601. }
  602. // Converts an RGB object to an HSB object
  603. function rgb2hsb(rgb) {
  604. var hsb = { h: 0, s: 0, b: 0 };
  605. var min = Math.min(rgb.r, rgb.g, rgb.b);
  606. var max = Math.max(rgb.r, rgb.g, rgb.b);
  607. var delta = max - min;
  608. hsb.b = max;
  609. hsb.s = max !== 0 ? 255 * delta / max : 0;
  610. if( hsb.s !== 0 ) {
  611. if( rgb.r === max ) {
  612. hsb.h = (rgb.g - rgb.b) / delta;
  613. } else if( rgb.g === max ) {
  614. hsb.h = 2 + (rgb.b - rgb.r) / delta;
  615. } else {
  616. hsb.h = 4 + (rgb.r - rgb.g) / delta;
  617. }
  618. } else {
  619. hsb.h = -1;
  620. }
  621. hsb.h *= 60;
  622. if( hsb.h < 0 ) {
  623. hsb.h += 360;
  624. }
  625. hsb.s *= 100/255;
  626. hsb.b *= 100/255;
  627. return hsb;
  628. }
  629. // Converts a hex string to an RGB object
  630. function hex2rgb(hex) {
  631. hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
  632. return {
  633. r: hex >> 16,
  634. g: (hex & 0x00FF00) >> 8,
  635. b: (hex & 0x0000FF)
  636. };
  637. }
  638. // Handle events
  639. $(document)
  640. // Hide on clicks outside of the control
  641. .on('mousedown.minicolors touchstart.minicolors', function(event) {
  642. if( !$(event.target).parents().add(event.target).hasClass('minicolors') ) {
  643. hide();
  644. }
  645. })
  646. // Start moving
  647. .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) {
  648. var target = $(this);
  649. event.preventDefault();
  650. $(document).data('minicolors-target', target);
  651. move(target, event, true);
  652. })
  653. // Move pickers
  654. .on('mousemove.minicolors touchmove.minicolors', function(event) {
  655. var target = $(document).data('minicolors-target');
  656. if( target ) move(target, event);
  657. })
  658. // Stop moving
  659. .on('mouseup.minicolors touchend.minicolors', function() {
  660. $(this).removeData('minicolors-target');
  661. })
  662. // Show panel when swatch is clicked
  663. .on('mousedown.minicolors touchstart.minicolors', '.minicolors-swatch', function(event) {
  664. var input = $(this).parent().find('.minicolors-input');
  665. event.preventDefault();
  666. show(input);
  667. })
  668. // Show on focus
  669. .on('focus.minicolors', '.minicolors-input', function() {
  670. var input = $(this);
  671. if( !input.data('minicolors-initialized') ) return;
  672. show(input);
  673. })
  674. // Fix hex on blur
  675. .on('blur.minicolors', '.minicolors-input', function() {
  676. var input = $(this),
  677. settings = input.data('minicolors-settings');
  678. if( !input.data('minicolors-initialized') ) return;
  679. // Parse Hex
  680. input.val(parseHex(input.val(), true));
  681. // Is it blank?
  682. if( input.val() === '' ) input.val(parseHex(settings.defaultValue, true));
  683. // Adjust case
  684. input.val( convertCase(input.val(), settings.letterCase) );
  685. })
  686. // Handle keypresses
  687. .on('keydown.minicolors', '.minicolors-input', function(event) {
  688. var input = $(this);
  689. if( !input.data('minicolors-initialized') ) return;
  690. switch(event.keyCode) {
  691. case 9: // tab
  692. hide();
  693. break;
  694. case 13: // enter
  695. case 27: // esc
  696. hide();
  697. input.blur();
  698. break;
  699. }
  700. })
  701. // Update on keyup
  702. .on('keyup.minicolors', '.minicolors-input', function() {
  703. var input = $(this);
  704. if( !input.data('minicolors-initialized') ) return;
  705. updateFromInput(input, true);
  706. })
  707. // Update on paste
  708. .on('paste.minicolors', '.minicolors-input', function() {
  709. var input = $(this);
  710. if( !input.data('minicolors-initialized') ) return;
  711. setTimeout( function() {
  712. updateFromInput(input, true);
  713. }, 1);
  714. });
  715. })(jQuery);