[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 /*! Iris Color Picker - v1.0.7 - 2014-11-28 2 * https://github.com/Automattic/Iris 3 * Copyright (c) 2014 Matt Wiebe; Licensed GPLv2 */ 4 (function( $, undef ){ 5 var _html, nonGradientIE, gradientType, vendorPrefixes, _css, Iris, UA, isIE, IEVersion; 6 7 _html = '<div class="iris-picker"><div class="iris-picker-inner"><div class="iris-square"><a class="iris-square-value" href="#"><span class="iris-square-handle ui-slider-handle"></span></a><div class="iris-square-inner iris-square-horiz"></div><div class="iris-square-inner iris-square-vert"></div></div><div class="iris-slider iris-strip"><div class="iris-slider-offset"></div></div></div></div>'; 8 _css = '.iris-picker{display:block;position:relative}.iris-picker,.iris-picker *{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input+.iris-picker{margin-top:4px}.iris-error{background-color:#ffafaf}.iris-border{border-radius:3px;border:1px solid #aaa;width:200px;background-color:#fff}.iris-picker-inner{position:absolute;top:0;right:0;left:0;bottom:0}.iris-border .iris-picker-inner{top:10px;right:10px;left:10px;bottom:10px}.iris-picker .iris-square-inner{position:absolute;left:0;right:0;top:0;bottom:0}.iris-picker .iris-square,.iris-picker .iris-slider,.iris-picker .iris-square-inner,.iris-picker .iris-palette{border-radius:3px;box-shadow:inset 0 0 5px rgba(0,0,0,.4);height:100%;width:12.5%;float:left;margin-right:5%}.iris-picker .iris-square{width:76%;margin-right:10%;position:relative}.iris-picker .iris-square-inner{width:auto;margin:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-square-inner,.iris-ie-9 .iris-palette{box-shadow:none;border-radius:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-palette{outline:1px solid rgba(0,0,0,.1)}.iris-ie-lt9 .iris-square,.iris-ie-lt9 .iris-slider,.iris-ie-lt9 .iris-square-inner,.iris-ie-lt9 .iris-palette{outline:1px solid #aaa}.iris-ie-lt9 .iris-square .ui-slider-handle{outline:1px solid #aaa;background-color:#fff;-ms-filter:"alpha(Opacity=30)"}.iris-ie-lt9 .iris-square .iris-square-handle{background:0;border:3px solid #fff;-ms-filter:"alpha(Opacity=50)"}.iris-picker .iris-strip{margin-right:0;position:relative}.iris-picker .iris-strip .ui-slider-handle{position:absolute;background:0;margin:0;right:-3px;left:-3px;border:4px solid #aaa;border-width:4px 3px;width:auto;height:6px;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.2);opacity:.9;z-index:5;cursor:ns-resize}.iris-strip .ui-slider-handle:before{content:" ";position:absolute;left:-2px;right:-2px;top:-3px;bottom:-3px;border:2px solid #fff;border-radius:3px}.iris-picker .iris-slider-offset{position:absolute;top:11px;left:0;right:0;bottom:-3px;width:auto;height:auto;background:transparent;border:0;border-radius:0}.iris-picker .iris-square-handle{background:transparent;border:5px solid #aaa;border-radius:50%;border-color:rgba(128,128,128,.5);box-shadow:none;width:12px;height:12px;position:absolute;left:-10px;top:-10px;cursor:move;opacity:1;z-index:10}.iris-picker .ui-state-focus .iris-square-handle{opacity:.8}.iris-picker .iris-square-handle:hover{border-color:#999}.iris-picker .iris-square-value:focus .iris-square-handle{box-shadow:0 0 2px rgba(0,0,0,.75);opacity:.8}.iris-picker .iris-square-handle:hover::after{border-color:#fff}.iris-picker .iris-square-handle::after{position:absolute;bottom:-4px;right:-4px;left:-4px;top:-4px;border:3px solid #f9f9f9;border-color:rgba(255,255,255,.8);border-radius:50%;content:" "}.iris-picker .iris-square-value{width:8px;height:8px;position:absolute}.iris-ie-lt9 .iris-square-value,.iris-mozilla .iris-square-value{width:1px;height:1px}.iris-palette-container{position:absolute;bottom:0;left:0;margin:0;padding:0}.iris-border .iris-palette-container{left:10px;bottom:10px}.iris-picker .iris-palette{margin:0;cursor:pointer}.iris-square-handle,.ui-slider-handle{border:0;outline:0}'; 9 10 // Even IE9 dosen't support gradients. Elaborate sigh. 11 UA = navigator.userAgent.toLowerCase(); 12 isIE = navigator.appName === 'Microsoft Internet Explorer'; 13 IEVersion = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0; 14 nonGradientIE = ( isIE && IEVersion < 10 ); 15 gradientType = false; 16 17 // we don't bother with an unprefixed version, as it has a different syntax 18 vendorPrefixes = [ '-moz-', '-webkit-', '-o-', '-ms-' ]; 19 20 // Bail for IE <= 7 21 if ( nonGradientIE && IEVersion <= 7 ) { 22 $.fn.iris = $.noop; 23 $.support.iris = false; 24 return; 25 } 26 27 $.support.iris = true; 28 29 function testGradientType() { 30 var el, base, 31 bgImageString = 'backgroundImage'; 32 33 if ( nonGradientIE ) { 34 gradientType = 'filter'; 35 } 36 else { 37 el = $( '<div id="iris-gradtest" />' ); 38 base = 'linear-gradient(top,#fff,#000)'; 39 $.each( vendorPrefixes, function( i, val ){ 40 el.css( bgImageString, val + base ); 41 if ( el.css( bgImageString ).match( 'gradient' ) ) { 42 gradientType = i; 43 return false; 44 } 45 }); 46 // check for legacy webkit gradient syntax 47 if ( gradientType === false ) { 48 el.css( 'background', '-webkit-gradient(linear,0% 0%,0% 100%,from(#fff),to(#000))' ); 49 if ( el.css( bgImageString ).match( 'gradient' ) ) { 50 gradientType = 'webkit'; 51 } 52 } 53 el.remove(); 54 } 55 56 } 57 58 /** 59 * Only for CSS3 gradients. oldIE will use a separate function. 60 * 61 * Accepts as many color stops as necessary from 2nd arg on, or 2nd 62 * arg can be an array of color stops 63 * 64 * @param {string} origin Gradient origin - top or left, defaults to left. 65 * @return {string} Appropriate CSS3 gradient string for use in 66 */ 67 function createGradient( origin, stops ) { 68 origin = ( origin === 'top' ) ? 'top' : 'left'; 69 stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); 70 if ( gradientType === 'webkit' ) { 71 return legacyWebkitGradient( origin, stops ); 72 } else { 73 return vendorPrefixes[ gradientType ] + 'linear-gradient(' + origin + ', ' + stops.join(', ') + ')'; 74 } 75 } 76 77 /** 78 * Stupid gradients for a stupid browser. 79 */ 80 function stupidIEGradient( origin, stops ) { 81 var type, self, lastIndex, filter, startPosProp, endPosProp, dimensionProp, template, html; 82 83 origin = ( origin === 'top' ) ? 'top' : 'left'; 84 stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); 85 // 8 hex: AARRGGBB 86 // GradientType: 0 vertical, 1 horizontal 87 type = ( origin === 'top' ) ? 0 : 1; 88 self = $( this ); 89 lastIndex = stops.length - 1; 90 filter = 'filter'; 91 startPosProp = ( type === 1 ) ? 'left' : 'top'; 92 endPosProp = ( type === 1 ) ? 'right' : 'bottom'; 93 dimensionProp = ( type === 1 ) ? 'height' : 'width'; 94 template = '<div class="iris-ie-gradient-shim" style="position:absolute;' + dimensionProp + ':100%;' + startPosProp + ':%start%;' + endPosProp + ':%end%;' + filter + ':%filter%;" data-color:"%color%"></div>'; 95 html = ''; 96 // need a positioning context 97 if ( self.css('position') === 'static' ) { 98 self.css( {position: 'relative' } ); 99 } 100 101 stops = fillColorStops( stops ); 102 $.each(stops, function( i, startColor ) { 103 var endColor, endStop, filterVal; 104 105 // we want two at a time. if we're on the last pair, bail. 106 if ( i === lastIndex ) { 107 return false; 108 } 109 110 endColor = stops[ i + 1 ]; 111 //if our pairs are at the same color stop, moving along. 112 if ( startColor.stop === endColor.stop ) { 113 return; 114 } 115 116 endStop = 100 - parseFloat( endColor.stop ) + '%'; 117 startColor.octoHex = new Color( startColor.color ).toIEOctoHex(); 118 endColor.octoHex = new Color( endColor.color ).toIEOctoHex(); 119 120 filterVal = 'progid:DXImageTransform.Microsoft.Gradient(GradientType=' + type + ', StartColorStr=\'' + startColor.octoHex + '\', EndColorStr=\'' + endColor.octoHex + '\')'; 121 html += template.replace( '%start%', startColor.stop ).replace( '%end%', endStop ).replace( '%filter%', filterVal ); 122 }); 123 self.find( '.iris-ie-gradient-shim' ).remove(); 124 $( html ).prependTo( self ); 125 } 126 127 function legacyWebkitGradient( origin, colorList ) { 128 var stops = []; 129 origin = ( origin === 'top' ) ? '0% 0%,0% 100%,' : '0% 100%,100% 100%,'; 130 colorList = fillColorStops( colorList ); 131 $.each( colorList, function( i, val ){ 132 stops.push( 'color-stop(' + ( parseFloat( val.stop ) / 100 ) + ', ' + val.color + ')' ); 133 }); 134 return '-webkit-gradient(linear,' + origin + stops.join(',') + ')'; 135 } 136 137 function fillColorStops( colorList ) { 138 var colors = [], 139 percs = [], 140 newColorList = [], 141 lastIndex = colorList.length - 1; 142 143 $.each( colorList, function( index, val ) { 144 var color = val, 145 perc = false, 146 match = val.match( /1?[0-9]{1,2}%$/ ); 147 148 if ( match ) { 149 color = val.replace( /\s?1?[0-9]{1,2}%$/, '' ); 150 perc = match.shift(); 151 } 152 colors.push( color ); 153 percs.push( perc ); 154 }); 155 156 // back fill first and last 157 if ( percs[0] === false ) { 158 percs[0] = '0%'; 159 } 160 161 if ( percs[lastIndex] === false ) { 162 percs[lastIndex] = '100%'; 163 } 164 165 percs = backFillColorStops( percs ); 166 167 $.each( percs, function( i ){ 168 newColorList[i] = { color: colors[i], stop: percs[i] }; 169 }); 170 return newColorList; 171 } 172 173 function backFillColorStops( stops ) { 174 var first = 0, 175 last = stops.length - 1, 176 i = 0, 177 foundFirst = false, 178 incr, 179 steps, 180 step, 181 firstVal; 182 183 if ( stops.length <= 2 || $.inArray( false, stops ) < 0 ) { 184 return stops; 185 } 186 while ( i < stops.length - 1 ) { 187 if ( ! foundFirst && stops[i] === false ) { 188 first = i - 1; 189 foundFirst = true; 190 } else if ( foundFirst && stops[i] !== false ) { 191 last = i; 192 i = stops.length; 193 } 194 i++; 195 } 196 steps = last - first; 197 firstVal = parseInt( stops[first].replace('%'), 10 ); 198 incr = ( parseFloat( stops[last].replace('%') ) - firstVal ) / steps; 199 i = first + 1; 200 step = 1; 201 while ( i < last ) { 202 stops[i] = ( firstVal + ( step * incr ) ) + '%'; 203 step++; 204 i++; 205 } 206 return backFillColorStops( stops ); 207 } 208 209 $.fn.gradient = function() { 210 var args = arguments; 211 return this.each( function() { 212 // this'll be oldishIE 213 if ( nonGradientIE ) { 214 stupidIEGradient.apply( this, args ); 215 } else { 216 // new hotness 217 $( this ).css( 'backgroundImage', createGradient.apply( this, args ) ); 218 } 219 }); 220 }; 221 222 $.fn.raninbowGradient = function( origin, args ) { 223 var opts, template, i, steps; 224 225 origin = origin || 'top'; 226 opts = $.extend( {}, { s: 100, l: 50 }, args ); 227 template = 'hsl(%h%,' + opts.s + '%,' + opts.l + '%)'; 228 i = 0; 229 steps = []; 230 while ( i <= 360 ) { 231 steps.push( template.replace('%h%', i) ); 232 i += 30; 233 } 234 return this.each(function() { 235 $(this).gradient( origin, steps ); 236 }); 237 }; 238 239 // the colorpicker widget def. 240 Iris = { 241 options: { 242 color: false, 243 mode: 'hsl', 244 controls: { 245 horiz: 's', // horizontal defaults to saturation 246 vert: 'l', // vertical defaults to lightness 247 strip: 'h' // right strip defaults to hue 248 }, 249 hide: true, // hide the color picker by default 250 border: true, // draw a border around the collection of UI elements 251 target: false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input. 252 width: 200, // the width of the collection of UI elements 253 palettes: false // show a palette of basic colors beneath the square. 254 }, 255 _color: '', 256 _palettes: [ '#000', '#fff', '#d33', '#d93', '#ee2', '#81d742', '#1e73be', '#8224e3' ], 257 _inited: false, 258 _defaultHSLControls: { 259 horiz: 's', 260 vert: 'l', 261 strip: 'h' 262 }, 263 _defaultHSVControls: { 264 horiz: 'h', 265 vert: 'v', 266 strip: 's' 267 }, 268 _scale: { 269 h: 360, 270 s: 100, 271 l: 100, 272 v: 100 273 }, 274 _create: function() { 275 var self = this, 276 el = self.element, 277 color = self.options.color || el.val(); 278 279 if ( gradientType === false ) { 280 testGradientType(); 281 } 282 283 if ( el.is( 'input' ) ) { 284 if ( self.options.target ) { 285 self.picker = $( _html ).appendTo( self.options.target ); 286 } else { 287 self.picker = $( _html ).insertAfter( el ); 288 } 289 290 self._addInputListeners( el ); 291 } else { 292 el.append( _html ); 293 self.picker = el.find( '.iris-picker' ); 294 } 295 296 // Browsers / Versions 297 // Feature detection doesn't work for these, and $.browser is deprecated 298 if ( isIE ) { 299 if ( IEVersion === 9 ) { 300 self.picker.addClass( 'iris-ie-9' ); 301 } else if ( IEVersion <= 8 ) { 302 self.picker.addClass( 'iris-ie-lt9' ); 303 } 304 } else if ( UA.indexOf('compatible') < 0 && UA.indexOf('khtml') < 0 && UA.match( /mozilla/ ) ) { 305 self.picker.addClass( 'iris-mozilla' ); 306 } 307 308 if ( self.options.palettes ) { 309 self._addPalettes(); 310 } 311 312 self._color = new Color( color ).setHSpace( self.options.mode ); 313 self.options.color = self._color.toString(); 314 315 // prep 'em for re-use 316 self.controls = { 317 square: self.picker.find( '.iris-square' ), 318 squareDrag: self.picker.find( '.iris-square-value' ), 319 horiz: self.picker.find( '.iris-square-horiz' ), 320 vert: self.picker.find( '.iris-square-vert' ), 321 strip: self.picker.find( '.iris-strip' ), 322 stripSlider: self.picker.find( '.iris-strip .iris-slider-offset' ) 323 }; 324 325 // small sanity check - if we chose hsv, change default controls away from hsl 326 if ( self.options.mode === 'hsv' && self._has('l', self.options.controls) ) { 327 self.options.controls = self._defaultHSVControls; 328 } else if ( self.options.mode === 'hsl' && self._has('v', self.options.controls) ) { 329 self.options.controls = self._defaultHSLControls; 330 } 331 332 // store it. HSL gets squirrely 333 self.hue = self._color.h(); 334 335 if ( self.options.hide ) { 336 self.picker.hide(); 337 } 338 339 if ( self.options.border ) { 340 self.picker.addClass( 'iris-border' ); 341 } 342 343 self._initControls(); 344 self.active = 'external'; 345 self._dimensions(); 346 self._change(); 347 }, 348 _has: function(needle, haystack) { 349 var ret = false; 350 $.each(haystack, function(i,v){ 351 if ( needle === v ) { 352 ret = true; 353 // exit the loop 354 return false; 355 } 356 }); 357 return ret; 358 }, 359 _addPalettes: function () { 360 var container = $( '<div class="iris-palette-container" />' ), 361 palette = $( '<a class="iris-palette" tabindex="0" />' ), 362 colors = $.isArray( this.options.palettes ) ? this.options.palettes : this._palettes; 363 364 // do we have an existing container? Empty and reuse it. 365 if ( this.picker.find( '.iris-palette-container' ).length ) { 366 container = this.picker.find( '.iris-palette-container' ).detach().html( '' ); 367 } 368 369 $.each(colors, function(index, val) { 370 palette.clone().data( 'color', val ) 371 .css( 'backgroundColor', val ).appendTo( container ) 372 .height( 10 ).width( 10 ); 373 }); 374 375 this.picker.append(container); 376 }, 377 _paint: function() { 378 var self = this; 379 self._paintDimension( 'top', 'strip' ); 380 self._paintDimension( 'top', 'vert' ); 381 self._paintDimension( 'left', 'horiz' ); 382 }, 383 _paintDimension: function( origin, control ) { 384 var self = this, 385 c = self._color, 386 mode = self.options.mode, 387 color = self._getHSpaceColor(), 388 target = self.controls[ control ], 389 controlOpts = self.options.controls, 390 stops; 391 392 // don't paint the active control 393 if ( control === self.active || ( self.active === 'square' && control !== 'strip' ) ) { 394 return; 395 } 396 397 switch ( controlOpts[ control ] ) { 398 case 'h': 399 if ( mode === 'hsv' ) { 400 color = c.clone(); 401 switch ( control ) { 402 case 'horiz': 403 color[controlOpts.vert](100); 404 break; 405 case 'vert': 406 color[controlOpts.horiz](100); 407 break; 408 case 'strip': 409 color.setHSpace('hsl'); 410 break; 411 } 412 stops = color.toHsl(); 413 } else { 414 if ( control === 'strip' ) { 415 stops = { s: color.s, l: color.l }; 416 } else { 417 stops = { s: 100, l: color.l }; 418 } 419 } 420 421 target.raninbowGradient( origin, stops ); 422 break; 423 case 's': 424 if ( mode === 'hsv' ) { 425 if ( control === 'vert' ) { 426 stops = [ c.clone().a(0).s(0).toCSS('rgba'), c.clone().a(1).s(0).toCSS('rgba') ]; 427 } else if ( control === 'strip' ) { 428 stops = [ c.clone().s(100).toCSS('hsl'), c.clone().s(0).toCSS('hsl') ]; 429 } else if ( control === 'horiz' ) { 430 stops = [ '#fff', 'hsl(' + color.h + ',100%,50%)' ]; 431 } 432 } else { // implicit mode === 'hsl' 433 if ( control === 'vert' && self.options.controls.horiz === 'h' ) { 434 stops = ['hsla(0, 0%, ' + color.l + '%, 0)', 'hsla(0, 0%, ' + color.l + '%, 1)']; 435 } else { 436 stops = ['hsl('+ color.h +',0%,50%)', 'hsl(' + color.h + ',100%,50%)']; 437 } 438 } 439 440 441 target.gradient( origin, stops ); 442 break; 443 case 'l': 444 if ( control === 'strip' ) { 445 stops = ['hsl(' + color.h + ',100%,100%)', 'hsl(' + color.h + ', ' + color.s + '%,50%)', 'hsl('+ color.h +',100%,0%)']; 446 } else { 447 stops = ['#fff', 'rgba(255,255,255,0) 50%', 'rgba(0,0,0,0) 50%', 'rgba(0,0,0,1)']; 448 } 449 target.gradient( origin, stops ); 450 break; 451 case 'v': 452 if ( control === 'strip' ) { 453 stops = [ c.clone().v(100).toCSS(), c.clone().v(0).toCSS() ]; 454 } else { 455 stops = ['rgba(0,0,0,0)', '#000']; 456 } 457 target.gradient( origin, stops ); 458 break; 459 default: 460 break; 461 } 462 }, 463 464 _getHSpaceColor: function() { 465 return ( this.options.mode === 'hsv' ) ? this._color.toHsv() : this._color.toHsl(); 466 }, 467 468 _dimensions: function( reset ) { 469 // whatever size 470 var self = this, 471 opts = self.options, 472 controls = self.controls, 473 square = controls.square, 474 strip = self.picker.find( '.iris-strip' ), 475 squareWidth = '77.5%', 476 stripWidth = '12%', 477 totalPadding = 20, 478 innerWidth = opts.border ? opts.width - totalPadding : opts.width, 479 controlsHeight, 480 paletteCount = $.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length, 481 paletteMargin, paletteWidth, paletteContainerWidth; 482 483 if ( reset ) { 484 square.css( 'width', '' ); 485 strip.css( 'width', '' ); 486 self.picker.css( {width: '', height: ''} ); 487 } 488 489 squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 ); 490 stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 ); 491 controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth; 492 493 square.width( squareWidth ).height( squareWidth ); 494 strip.height( squareWidth ).width( stripWidth ); 495 self.picker.css( { width: opts.width, height: controlsHeight } ); 496 497 if ( ! opts.palettes ) { 498 return self.picker.css( 'paddingBottom', '' ); 499 } 500 501 // single margin at 2% 502 paletteMargin = squareWidth * 2 / 100; 503 paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin ); 504 paletteWidth = paletteContainerWidth / paletteCount; 505 self.picker.find('.iris-palette').each( function( i ) { 506 var margin = i === 0 ? 0 : paletteMargin; 507 $( this ).css({ 508 width: paletteWidth, 509 height: paletteWidth, 510 marginLeft: margin 511 }); 512 }); 513 self.picker.css( 'paddingBottom', paletteWidth + paletteMargin ); 514 strip.height( paletteWidth + paletteMargin + squareWidth ); 515 }, 516 517 _addInputListeners: function( input ) { 518 var self = this, 519 debounceTimeout = 100, 520 callback = function( event ){ 521 var color = new Color( input.val() ), 522 val = input.val().replace( /^#/, '' ); 523 524 input.removeClass( 'iris-error' ); 525 // we gave a bad color 526 if ( color.error ) { 527 // don't error on an empty input - we want those allowed 528 if ( val !== '' ) { 529 input.addClass( 'iris-error' ); 530 } 531 } else { 532 if ( color.toString() !== self._color.toString() ) { 533 // let's not do this on keyup for hex shortcodes 534 if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { 535 self._setOption( 'color', color.toString() ); 536 } 537 } 538 } 539 }; 540 541 input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); 542 543 // If we initialized hidden, show on first focus. The rest is up to you. 544 if ( self.options.hide ) { 545 input.one( 'focus', function() { 546 self.show(); 547 }); 548 } 549 }, 550 551 _initControls: function() { 552 var self = this, 553 controls = self.controls, 554 square = controls.square, 555 controlOpts = self.options.controls, 556 stripScale = self._scale[controlOpts.strip]; 557 558 controls.stripSlider.slider({ 559 orientation: 'vertical', 560 max: stripScale, 561 slide: function( event, ui ) { 562 self.active = 'strip'; 563 // "reverse" for hue. 564 if ( controlOpts.strip === 'h' ) { 565 ui.value = stripScale - ui.value; 566 } 567 568 self._color[controlOpts.strip]( ui.value ); 569 self._change.apply( self, arguments ); 570 } 571 }); 572 573 controls.squareDrag.draggable({ 574 containment: controls.square.find( '.iris-square-inner' ), 575 zIndex: 1000, 576 cursor: 'move', 577 drag: function( event, ui ) { 578 self._squareDrag( event, ui ); 579 }, 580 start: function() { 581 square.addClass( 'iris-dragging' ); 582 $(this).addClass( 'ui-state-focus' ); 583 }, 584 stop: function() { 585 square.removeClass( 'iris-dragging' ); 586 $(this).removeClass( 'ui-state-focus' ); 587 } 588 }).on( 'mousedown mouseup', function( event ) { 589 var focusClass = 'ui-state-focus'; 590 event.preventDefault(); 591 if (event.type === 'mousedown' ) { 592 self.picker.find( '.' + focusClass ).removeClass( focusClass ).blur(); 593 $(this).addClass( focusClass ).focus(); 594 } else { 595 $(this).removeClass( focusClass ); 596 } 597 }).on( 'keydown', function( event ) { 598 var container = controls.square, 599 draggable = controls.squareDrag, 600 position = draggable.position(), 601 distance = self.options.width / 100; // Distance in pixels the draggable should be moved: 1 "stop" 602 603 // make alt key go "10" 604 if ( event.altKey ) { 605 distance *= 10; 606 } 607 608 // Reposition if one of the directional keys is pressed 609 switch ( event.keyCode ) { 610 case 37: position.left -= distance; break; // Left 611 case 38: position.top -= distance; break; // Up 612 case 39: position.left += distance; break; // Right 613 case 40: position.top += distance; break; // Down 614 default: return true; // Exit and bubble 615 } 616 617 // Keep draggable within container 618 position.left = Math.max( 0, Math.min( position.left, container.width() ) ); 619 position.top = Math.max( 0, Math.min( position.top, container.height() ) ); 620 621 draggable.css(position); 622 self._squareDrag( event, { position: position }); 623 event.preventDefault(); 624 }); 625 626 // allow clicking on the square to move there and keep dragging 627 square.mousedown( function( event ) { 628 var squareOffset, pos; 629 // only left click 630 if ( event.which !== 1 ) { 631 return; 632 } 633 634 // prevent bubbling from the handle: no infinite loops 635 if ( ! $( event.target ).is( 'div' ) ) { 636 return; 637 } 638 639 squareOffset = self.controls.square.offset(); 640 pos = { 641 top: event.pageY - squareOffset.top, 642 left: event.pageX - squareOffset.left 643 }; 644 event.preventDefault(); 645 self._squareDrag( event, { position: pos } ); 646 event.target = self.controls.squareDrag.get(0); 647 self.controls.squareDrag.css( pos ).trigger( event ); 648 }); 649 650 // palettes 651 if ( self.options.palettes ) { 652 self._paletteListeners(); 653 } 654 }, 655 656 _paletteListeners: function() { 657 var self = this; 658 self.picker.find('.iris-palette-container').on('click.palette', '.iris-palette', function() { 659 self._color.fromCSS( $(this).data('color') ); 660 self.active = 'external'; 661 self._change(); 662 }).on( 'keydown.palette', '.iris-palette', function( event ) { 663 if ( ! ( event.keyCode === 13 || event.keyCode === 32 ) ) { 664 return true; 665 } 666 event.stopPropagation(); 667 $( this ).click(); 668 }); 669 }, 670 671 _squareDrag: function( event, ui ) { 672 var self = this, 673 controlOpts = self.options.controls, 674 dimensions = self._squareDimensions(), 675 vertVal = Math.round( ( dimensions.h - ui.position.top ) / dimensions.h * self._scale[controlOpts.vert] ), 676 horizVal = self._scale[controlOpts.horiz] - Math.round( ( dimensions.w - ui.position.left ) / dimensions.w * self._scale[controlOpts.horiz] ); 677 678 self._color[controlOpts.horiz]( horizVal )[controlOpts.vert]( vertVal ); 679 680 self.active = 'square'; 681 self._change.apply( self, arguments ); 682 }, 683 684 _setOption: function( key, value ) { 685 var self = this, 686 oldValue = self.options[key], 687 doDimensions = false, 688 hexLessColor, 689 newColor, 690 method; 691 692 // ensure the new value is set. We can reset to oldValue if some check wasn't met. 693 self.options[key] = value; 694 695 switch(key) { 696 case 'color': 697 // cast to string in case we have a number 698 value = '' + value; 699 hexLessColor = value.replace( /^#/, '' ); 700 newColor = new Color( value ).setHSpace( self.options.mode ); 701 if ( newColor.error ) { 702 self.options[key] = oldValue; 703 } else { 704 self._color = newColor; 705 self.options.color = self.options[key] = self._color.toString(); 706 self.active = 'external'; 707 self._change(); 708 } 709 break; 710 case 'palettes': 711 doDimensions = true; 712 713 if ( value ) { 714 self._addPalettes(); 715 } else { 716 self.picker.find('.iris-palette-container').remove(); 717 } 718 719 // do we need to add events? 720 if ( ! oldValue ) { 721 self._paletteListeners(); 722 } 723 break; 724 case 'width': 725 doDimensions = true; 726 break; 727 case 'border': 728 doDimensions = true; 729 method = value ? 'addClass' : 'removeClass'; 730 self.picker[method]('iris-border'); 731 break; 732 case 'mode': 733 case 'controls': 734 // if nothing's changed, let's bail, since this causes re-rendering the whole widget 735 if ( oldValue === value ) { 736 return; 737 } 738 739 // we're using these poorly named variables because they're already scoped. 740 // method is the element that Iris was called on. oldValue will be the options 741 method = self.element; 742 oldValue = self.options; 743 oldValue.hide = ! self.picker.is( ':visible' ); 744 self.destroy(); 745 self.picker.remove(); 746 return $(self.element).iris(oldValue); 747 } 748 749 // Do we need to recalc dimensions? 750 if ( doDimensions ) { 751 self._dimensions(true); 752 } 753 }, 754 755 _squareDimensions: function( forceRefresh ) { 756 var square = this.controls.square, 757 dimensions, 758 control; 759 760 if ( forceRefresh !== undef && square.data('dimensions') ) { 761 return square.data('dimensions'); 762 } 763 764 control = this.controls.squareDrag; 765 dimensions = { 766 w: square.width(), 767 h: square.height() 768 }; 769 square.data( 'dimensions', dimensions ); 770 return dimensions; 771 }, 772 773 _isNonHueControl: function( active, type ) { 774 if ( active === 'square' && this.options.controls.strip === 'h' ) { 775 return true; 776 } else if ( type === 'external' || ( type === 'h' && active === 'strip' ) ) { 777 return false; 778 } 779 780 return true; 781 }, 782 783 _change: function() { 784 var self = this, 785 controls = self.controls, 786 color = self._getHSpaceColor(), 787 actions = [ 'square', 'strip' ], 788 controlOpts = self.options.controls, 789 type = controlOpts[self.active] || 'external', 790 oldHue = self.hue; 791 792 if ( self.active === 'strip' ) { 793 // take no action on any of the square sliders if we adjusted the strip 794 actions = []; 795 } else if ( self.active !== 'external' ) { 796 // for non-strip, non-external, strip should never change 797 actions.pop(); // conveniently the last item 798 } 799 800 $.each( actions, function(index, item) { 801 var value, dimensions, cssObj; 802 if ( item !== self.active ) { 803 switch ( item ) { 804 case 'strip': 805 // reverse for hue 806 value = ( controlOpts.strip === 'h' ) ? self._scale[controlOpts.strip] - color[controlOpts.strip] : color[controlOpts.strip]; 807 controls.stripSlider.slider( 'value', value ); 808 break; 809 case 'square': 810 dimensions = self._squareDimensions(); 811 cssObj = { 812 left: color[controlOpts.horiz] / self._scale[controlOpts.horiz] * dimensions.w, 813 top: dimensions.h - ( color[controlOpts.vert] / self._scale[controlOpts.vert] * dimensions.h ) 814 }; 815 816 self.controls.squareDrag.css( cssObj ); 817 break; 818 } 819 } 820 }); 821 822 // Ensure that we don't change hue if we triggered a hue reset 823 if ( color.h !== oldHue && self._isNonHueControl( self.active, type ) ) { 824 self._color.h(oldHue); 825 } 826 827 // store hue for repeating above check next time 828 self.hue = self._color.h(); 829 830 self.options.color = self._color.toString(); 831 832 // only run after the first time 833 if ( self._inited ) { 834 self._trigger( 'change', { type: self.active }, { color: self._color } ); 835 } 836 837 if ( self.element.is( ':input' ) && ! self._color.error ) { 838 self.element.removeClass( 'iris-error' ); 839 if ( self.element.val() !== self._color.toString() ) { 840 self.element.val( self._color.toString() ); 841 } 842 } 843 844 self._paint(); 845 self._inited = true; 846 self.active = false; 847 }, 848 // taken from underscore.js _.debounce method 849 _debounce: function( func, wait, immediate ) { 850 var timeout, result; 851 return function() { 852 var context = this, 853 args = arguments, 854 later, 855 callNow; 856 857 later = function() { 858 timeout = null; 859 if ( ! immediate) { 860 result = func.apply( context, args ); 861 } 862 }; 863 864 callNow = immediate && !timeout; 865 clearTimeout( timeout ); 866 timeout = setTimeout( later, wait ); 867 if ( callNow ) { 868 result = func.apply( context, args ); 869 } 870 return result; 871 }; 872 }, 873 show: function() { 874 this.picker.show(); 875 }, 876 hide: function() { 877 this.picker.hide(); 878 }, 879 toggle: function() { 880 this.picker.toggle(); 881 }, 882 color: function(newColor) { 883 if ( newColor === true ) { 884 return this._color.clone(); 885 } else if ( newColor === undef ) { 886 return this._color.toString(); 887 } 888 this.option('color', newColor); 889 } 890 }; 891 // initialize the widget 892 $.widget( 'a8c.iris', Iris ); 893 // add CSS 894 $( '<style id="iris-css">' + _css + '</style>' ).appendTo( 'head' ); 895 896 }( jQuery )); 897 /*! Color.js - v0.9.11 - 2013-08-09 898 * https://github.com/Automattic/Color.js 899 * Copyright (c) 2013 Matt Wiebe; Licensed GPLv2 */ 900 (function(global, undef) { 901 902 var Color = function( color, type ) { 903 if ( ! ( this instanceof Color ) ) 904 return new Color( color, type ); 905 906 return this._init( color, type ); 907 }; 908 909 Color.fn = Color.prototype = { 910 _color: 0, 911 _alpha: 1, 912 error: false, 913 // for preserving hue/sat in fromHsl().toHsl() flows 914 _hsl: { h: 0, s: 0, l: 0 }, 915 // for preserving hue/sat in fromHsv().toHsv() flows 916 _hsv: { h: 0, s: 0, v: 0 }, 917 // for setting hsl or hsv space - needed for .h() & .s() functions to function properly 918 _hSpace: 'hsl', 919 _init: function( color ) { 920 var func = 'noop'; 921 switch ( typeof color ) { 922 case 'object': 923 // alpha? 924 if ( color.a !== undef ) 925 this.a( color.a ); 926 func = ( color.r !== undef ) ? 'fromRgb' : 927 ( color.l !== undef ) ? 'fromHsl' : 928 ( color.v !== undef ) ? 'fromHsv' : func; 929 return this[func]( color ); 930 case 'string': 931 return this.fromCSS( color ); 932 case 'number': 933 return this.fromInt( parseInt( color, 10 ) ); 934 } 935 return this; 936 }, 937 938 _error: function() { 939 this.error = true; 940 return this; 941 }, 942 943 clone: function() { 944 var newColor = new Color( this.toInt() ), 945 copy = ['_alpha', '_hSpace', '_hsl', '_hsv', 'error']; 946 for ( var i = copy.length - 1; i >= 0; i-- ) { 947 newColor[ copy[i] ] = this[ copy[i] ]; 948 } 949 return newColor; 950 }, 951 952 setHSpace: function( space ) { 953 this._hSpace = ( space === 'hsv' ) ? space : 'hsl'; 954 return this; 955 }, 956 957 noop: function() { 958 return this; 959 }, 960 961 fromCSS: function( color ) { 962 var list, 963 leadingRE = /^(rgb|hs(l|v))a?\(/; 964 this.error = false; 965 966 // whitespace and semicolon trim 967 color = color.replace(/^\s+/, '').replace(/\s+$/, '').replace(/;$/, ''); 968 969 if ( color.match(leadingRE) && color.match(/\)$/) ) { 970 list = color.replace(/(\s|%)/g, '').replace(leadingRE, '').replace(/,?\);?$/, '').split(','); 971 972 if ( list.length < 3 ) 973 return this._error(); 974 975 if ( list.length === 4 ) { 976 this.a( parseFloat( list.pop() ) ); 977 // error state has been set to true in .a() if we passed NaN 978 if ( this.error ) 979 return this; 980 } 981 982 for (var i = list.length - 1; i >= 0; i--) { 983 list[i] = parseInt(list[i], 10); 984 if ( isNaN( list[i] ) ) 985 return this._error(); 986 } 987 988 if ( color.match(/^rgb/) ) { 989 return this.fromRgb( { 990 r: list[0], 991 g: list[1], 992 b: list[2] 993 } ); 994 } else if ( color.match(/^hsv/) ) { 995 return this.fromHsv( { 996 h: list[0], 997 s: list[1], 998 v: list[2] 999 } ); 1000 } else { 1001 return this.fromHsl( { 1002 h: list[0], 1003 s: list[1], 1004 l: list[2] 1005 } ); 1006 } 1007 } else { 1008 // must be hex amirite? 1009 return this.fromHex( color ); 1010 } 1011 }, 1012 1013 fromRgb: function( rgb, preserve ) { 1014 if ( typeof rgb !== 'object' || rgb.r === undef || rgb.g === undef || rgb.b === undef ) 1015 return this._error(); 1016 1017 this.error = false; 1018 return this.fromInt( parseInt( ( rgb.r << 16 ) + ( rgb.g << 8 ) + rgb.b, 10 ), preserve ); 1019 }, 1020 1021 fromHex: function( color ) { 1022 color = color.replace(/^#/, '').replace(/^0x/, ''); 1023 if ( color.length === 3 ) { 1024 color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]; 1025 } 1026 1027 // rough error checking - this is where things go squirrely the most 1028 this.error = ! /^[0-9A-F]{6}$/i.test( color ); 1029 return this.fromInt( parseInt( color, 16 ) ); 1030 }, 1031 1032 fromHsl: function( hsl ) { 1033 var r, g, b, q, p, h, s, l; 1034 1035 if ( typeof hsl !== 'object' || hsl.h === undef || hsl.s === undef || hsl.l === undef ) 1036 return this._error(); 1037 1038 this._hsl = hsl; // store it 1039 this._hSpace = 'hsl'; // implicit 1040 h = hsl.h / 360; s = hsl.s / 100; l = hsl.l / 100; 1041 if ( s === 0 ) { 1042 r = g = b = l; // achromatic 1043 } 1044 else { 1045 q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s; 1046 p = 2 * l - q; 1047 r = this.hue2rgb( p, q, h + 1/3 ); 1048 g = this.hue2rgb( p, q, h ); 1049 b = this.hue2rgb( p, q, h - 1/3 ); 1050 } 1051 return this.fromRgb( { 1052 r: r * 255, 1053 g: g * 255, 1054 b: b * 255 1055 }, true ); // true preserves hue/sat 1056 }, 1057 1058 fromHsv: function( hsv ) { 1059 var h, s, v, r, g, b, i, f, p, q, t; 1060 if ( typeof hsv !== 'object' || hsv.h === undef || hsv.s === undef || hsv.v === undef ) 1061 return this._error(); 1062 1063 this._hsv = hsv; // store it 1064 this._hSpace = 'hsv'; // implicit 1065 1066 h = hsv.h / 360; s = hsv.s / 100; v = hsv.v / 100; 1067 i = Math.floor( h * 6 ); 1068 f = h * 6 - i; 1069 p = v * ( 1 - s ); 1070 q = v * ( 1 - f * s ); 1071 t = v * ( 1 - ( 1 - f ) * s ); 1072 1073 switch( i % 6 ) { 1074 case 0: 1075 r = v; g = t; b = p; 1076 break; 1077 case 1: 1078 r = q; g = v; b = p; 1079 break; 1080 case 2: 1081 r = p; g = v; b = t; 1082 break; 1083 case 3: 1084 r = p; g = q; b = v; 1085 break; 1086 case 4: 1087 r = t; g = p; b = v; 1088 break; 1089 case 5: 1090 r = v; g = p; b = q; 1091 break; 1092 } 1093 1094 return this.fromRgb( { 1095 r: r * 255, 1096 g: g * 255, 1097 b: b * 255 1098 }, true ); // true preserves hue/sat 1099 1100 }, 1101 // everything comes down to fromInt 1102 fromInt: function( color, preserve ) { 1103 this._color = parseInt( color, 10 ); 1104 1105 if ( isNaN( this._color ) ) 1106 this._color = 0; 1107 1108 // let's coerce things 1109 if ( this._color > 16777215 ) 1110 this._color = 16777215; 1111 else if ( this._color < 0 ) 1112 this._color = 0; 1113 1114 // let's not do weird things 1115 if ( preserve === undef ) { 1116 this._hsv.h = this._hsv.s = this._hsl.h = this._hsl.s = 0; 1117 } 1118 // EVENT GOES HERE 1119 return this; 1120 }, 1121 1122 hue2rgb: function( p, q, t ) { 1123 if ( t < 0 ) { 1124 t += 1; 1125 } 1126 if ( t > 1 ) { 1127 t -= 1; 1128 } 1129 if ( t < 1/6 ) { 1130 return p + ( q - p ) * 6 * t; 1131 } 1132 if ( t < 1/2 ) { 1133 return q; 1134 } 1135 if ( t < 2/3 ) { 1136 return p + ( q - p ) * ( 2/3 - t ) * 6; 1137 } 1138 return p; 1139 }, 1140 1141 toString: function() { 1142 var hex = parseInt( this._color, 10 ).toString( 16 ); 1143 if ( this.error ) 1144 return ''; 1145 // maybe left pad it 1146 if ( hex.length < 6 ) { 1147 for (var i = 6 - hex.length - 1; i >= 0; i--) { 1148 hex = '0' + hex; 1149 } 1150 } 1151 return '#' + hex; 1152 }, 1153 1154 toCSS: function( type, alpha ) { 1155 type = type || 'hex'; 1156 alpha = parseFloat( alpha || this._alpha ); 1157 switch ( type ) { 1158 case 'rgb': 1159 case 'rgba': 1160 var rgb = this.toRgb(); 1161 if ( alpha < 1 ) { 1162 return "rgba( " + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + alpha + " )"; 1163 } 1164 else { 1165 return "rgb( " + rgb.r + ", " + rgb.g + ", " + rgb.b + " )"; 1166 } 1167 break; 1168 case 'hsl': 1169 case 'hsla': 1170 var hsl = this.toHsl(); 1171 if ( alpha < 1 ) { 1172 return "hsla( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "%, " + alpha + " )"; 1173 } 1174 else { 1175 return "hsl( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "% )"; 1176 } 1177 break; 1178 default: 1179 return this.toString(); 1180 } 1181 }, 1182 1183 toRgb: function() { 1184 return { 1185 r: 255 & ( this._color >> 16 ), 1186 g: 255 & ( this._color >> 8 ), 1187 b: 255 & ( this._color ) 1188 }; 1189 }, 1190 1191 toHsl: function() { 1192 var rgb = this.toRgb(); 1193 var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; 1194 var max = Math.max( r, g, b ), min = Math.min( r, g, b ); 1195 var h, s, l = ( max + min ) / 2; 1196 1197 if ( max === min ) { 1198 h = s = 0; // achromatic 1199 } else { 1200 var d = max - min; 1201 s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ); 1202 switch ( max ) { 1203 case r: h = ( g - b ) / d + ( g < b ? 6 : 0 ); 1204 break; 1205 case g: h = ( b - r ) / d + 2; 1206 break; 1207 case b: h = ( r - g ) / d + 4; 1208 break; 1209 } 1210 h /= 6; 1211 } 1212 1213 // maintain hue & sat if we've been manipulating things in the HSL space. 1214 h = Math.round( h * 360 ); 1215 if ( h === 0 && this._hsl.h !== h ) { 1216 h = this._hsl.h; 1217 } 1218 s = Math.round( s * 100 ); 1219 if ( s === 0 && this._hsl.s ) { 1220 s = this._hsl.s; 1221 } 1222 1223 return { 1224 h: h, 1225 s: s, 1226 l: Math.round( l * 100 ) 1227 }; 1228 1229 }, 1230 1231 toHsv: function() { 1232 var rgb = this.toRgb(); 1233 var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255; 1234 var max = Math.max( r, g, b ), min = Math.min( r, g, b ); 1235 var h, s, v = max; 1236 var d = max - min; 1237 s = max === 0 ? 0 : d / max; 1238 1239 if ( max === min ) { 1240 h = s = 0; // achromatic 1241 } else { 1242 switch( max ){ 1243 case r: 1244 h = ( g - b ) / d + ( g < b ? 6 : 0 ); 1245 break; 1246 case g: 1247 h = ( b - r ) / d + 2; 1248 break; 1249 case b: 1250 h = ( r - g ) / d + 4; 1251 break; 1252 } 1253 h /= 6; 1254 } 1255 1256 // maintain hue & sat if we've been manipulating things in the HSV space. 1257 h = Math.round( h * 360 ); 1258 if ( h === 0 && this._hsv.h !== h ) { 1259 h = this._hsv.h; 1260 } 1261 s = Math.round( s * 100 ); 1262 if ( s === 0 && this._hsv.s ) { 1263 s = this._hsv.s; 1264 } 1265 1266 return { 1267 h: h, 1268 s: s, 1269 v: Math.round( v * 100 ) 1270 }; 1271 }, 1272 1273 toInt: function() { 1274 return this._color; 1275 }, 1276 1277 toIEOctoHex: function() { 1278 // AARRBBGG 1279 var hex = this.toString(); 1280 var AA = parseInt( 255 * this._alpha, 10 ).toString(16); 1281 if ( AA.length === 1 ) { 1282 AA = '0' + AA; 1283 } 1284 return '#' + AA + hex.replace(/^#/, '' ); 1285 }, 1286 1287 toLuminosity: function() { 1288 var rgb = this.toRgb(); 1289 return 0.2126 * Math.pow( rgb.r / 255, 2.2 ) + 0.7152 * Math.pow( rgb.g / 255, 2.2 ) + 0.0722 * Math.pow( rgb.b / 255, 2.2); 1290 }, 1291 1292 getDistanceLuminosityFrom: function( color ) { 1293 if ( ! ( color instanceof Color ) ) { 1294 throw 'getDistanceLuminosityFrom requires a Color object'; 1295 } 1296 var lum1 = this.toLuminosity(); 1297 var lum2 = color.toLuminosity(); 1298 if ( lum1 > lum2 ) { 1299 return ( lum1 + 0.05 ) / ( lum2 + 0.05 ); 1300 } 1301 else { 1302 return ( lum2 + 0.05 ) / ( lum1 + 0.05 ); 1303 } 1304 }, 1305 1306 getMaxContrastColor: function() { 1307 var lum = this.toLuminosity(); 1308 var hex = ( lum >= 0.5 ) ? '000000' : 'ffffff'; 1309 return new Color( hex ); 1310 }, 1311 1312 getReadableContrastingColor: function( bgColor, minContrast ) { 1313 if ( ! bgColor instanceof Color ) { 1314 return this; 1315 } 1316 1317 // you shouldn't use less than 5, but you might want to. 1318 var targetContrast = ( minContrast === undef ) ? 5 : minContrast; 1319 // working things 1320 var contrast = bgColor.getDistanceLuminosityFrom( this ); 1321 var maxContrastColor = bgColor.getMaxContrastColor(); 1322 var maxContrast = maxContrastColor.getDistanceLuminosityFrom( bgColor ); 1323 1324 // if current max contrast is less than the target contrast, we had wishful thinking. 1325 // still, go max 1326 if ( maxContrast <= targetContrast ) { 1327 return maxContrastColor; 1328 } 1329 // or, we might already have sufficient contrast 1330 else if ( contrast >= targetContrast ) { 1331 return this; 1332 } 1333 1334 var incr = ( 0 === maxContrastColor.toInt() ) ? -1 : 1; 1335 while ( contrast < targetContrast ) { 1336 this.l( incr, true ); // 2nd arg turns this into an incrementer 1337 contrast = this.getDistanceLuminosityFrom( bgColor ); 1338 // infininite loop prevention: you never know. 1339 if ( this._color === 0 || this._color === 16777215 ) { 1340 break; 1341 } 1342 } 1343 1344 return this; 1345 1346 }, 1347 1348 a: function( val ) { 1349 if ( val === undef ) 1350 return this._alpha; 1351 1352 var a = parseFloat( val ); 1353 1354 if ( isNaN( a ) ) 1355 return this._error(); 1356 1357 this._alpha = a; 1358 return this; 1359 }, 1360 1361 // TRANSFORMS 1362 1363 darken: function( amount ) { 1364 amount = amount || 5; 1365 return this.l( - amount, true ); 1366 }, 1367 1368 lighten: function( amount ) { 1369 amount = amount || 5; 1370 return this.l( amount, true ); 1371 }, 1372 1373 saturate: function( amount ) { 1374 amount = amount || 15; 1375 return this.s( amount, true ); 1376 }, 1377 1378 desaturate: function( amount ) { 1379 amount = amount || 15; 1380 return this.s( - amount, true ); 1381 }, 1382 1383 toGrayscale: function() { 1384 return this.setHSpace('hsl').s( 0 ); 1385 }, 1386 1387 getComplement: function() { 1388 return this.h( 180, true ); 1389 }, 1390 1391 getSplitComplement: function( step ) { 1392 step = step || 1; 1393 var incr = 180 + ( step * 30 ); 1394 return this.h( incr, true ); 1395 }, 1396 1397 getAnalog: function( step ) { 1398 step = step || 1; 1399 var incr = step * 30; 1400 return this.h( incr, true ); 1401 }, 1402 1403 getTetrad: function( step ) { 1404 step = step || 1; 1405 var incr = step * 60; 1406 return this.h( incr, true ); 1407 }, 1408 1409 getTriad: function( step ) { 1410 step = step || 1; 1411 var incr = step * 120; 1412 return this.h( incr, true ); 1413 }, 1414 1415 _partial: function( key ) { 1416 var prop = shortProps[key]; 1417 return function( val, incr ) { 1418 var color = this._spaceFunc('to', prop.space); 1419 1420 // GETTER 1421 if ( val === undef ) 1422 return color[key]; 1423 1424 // INCREMENT 1425 if ( incr === true ) 1426 val = color[key] + val; 1427 1428 // MOD & RANGE 1429 if ( prop.mod ) 1430 val = val % prop.mod; 1431 if ( prop.range ) 1432 val = ( val < prop.range[0] ) ? prop.range[0] : ( val > prop.range[1] ) ? prop.range[1] : val; 1433 1434 // NEW VALUE 1435 color[key] = val; 1436 1437 return this._spaceFunc('from', prop.space, color); 1438 }; 1439 }, 1440 1441 _spaceFunc: function( dir, s, val ) { 1442 var space = s || this._hSpace, 1443 funcName = dir + space.charAt(0).toUpperCase() + space.substr(1); 1444 return this[funcName](val); 1445 } 1446 }; 1447 1448 var shortProps = { 1449 h: { 1450 mod: 360 1451 }, 1452 s: { 1453 range: [0,100] 1454 }, 1455 l: { 1456 space: 'hsl', 1457 range: [0,100] 1458 }, 1459 v: { 1460 space: 'hsv', 1461 range: [0,100] 1462 }, 1463 r: { 1464 space: 'rgb', 1465 range: [0,255] 1466 }, 1467 g: { 1468 space: 'rgb', 1469 range: [0,255] 1470 }, 1471 b: { 1472 space: 'rgb', 1473 range: [0,255] 1474 } 1475 }; 1476 1477 for ( var key in shortProps ) { 1478 if ( shortProps.hasOwnProperty( key ) ) 1479 Color.fn[key] = Color.fn._partial(key); 1480 } 1481 1482 // play nicely with Node + browser 1483 if ( typeof exports === 'object' ) 1484 module.exports = Color; 1485 else 1486 global.Color = Color; 1487 1488 }(this));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body