1/** 2 * jquery.stapel.js v1.0.0 3 * http://www.codrops.com 4 * 5 * Licensed under the MIT license. 6 * http://www.opensource.org/licenses/mit-license.php 7 * 8 * Copyright 2012, Codrops 9 * http://www.codrops.com 10 */ 11;( function( $, window, undefined ) { 12 13 'use strict'; 14 15 /* 16 * debouncedresize: special jQuery event that happens once after a window resize 17 * 18 * latest version and complete README available on Github: 19 * https://github.com/louisremi/jquery-smartresize/blob/master/jquery.debouncedresize.js 20 * 21 * Copyright 2011 @louis_remi 22 * Licensed under the MIT license. 23 */ 24 var $event = $.event, 25 $special, 26 resizeTimeout; 27 28 $special = $event.special.debouncedresize = { 29 setup: function() { 30 $( this ).on( "resize", $special.handler ); 31 }, 32 teardown: function() { 33 $( this ).off( "resize", $special.handler ); 34 }, 35 handler: function( event, execAsap ) { 36 // Save the context 37 var context = this, 38 args = arguments, 39 dispatch = function() { 40 // set correct event type 41 event.type = "debouncedresize"; 42 $event.dispatch.apply( context, args ); 43 }; 44 45 if ( resizeTimeout ) { 46 clearTimeout( resizeTimeout ); 47 } 48 49 execAsap ? 50 dispatch() : 51 resizeTimeout = setTimeout( dispatch, $special.threshold ); 52 }, 53 threshold: 150 54 }; 55 56 // ======================= imagesLoaded Plugin =============================== 57 // https://github.com/desandro/imagesloaded 58 59 // $('#my-container').imagesLoaded(myFunction) 60 // execute a callback when all images have loaded. 61 // needed because .load() doesn't work on cached images 62 63 // callback function gets image collection as argument 64 // this is the container 65 66 // original: mit license. paul irish. 2010. 67 // contributors: Oren Solomianik, David DeSandro, Yiannis Chatzikonstantinou 68 69 // blank image data-uri bypasses webkit log warning (thx doug jones) 70 var BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; 71 72 $.fn.imagesLoaded = function( callback ) { 73 var $this = this, 74 deferred = $.isFunction($.Deferred) ? $.Deferred() : 0, 75 hasNotify = $.isFunction(deferred.notify), 76 $images = $this.find('img').add( $this.filter('img') ), 77 loaded = [], 78 proper = [], 79 broken = []; 80 81 // Register deferred callbacks 82 if ($.isPlainObject(callback)) { 83 $.each(callback, function (key, value) { 84 if (key === 'callback') { 85 callback = value; 86 } else if (deferred) { 87 deferred[key](value); 88 } 89 }); 90 } 91 92 function doneLoading() { 93 var $proper = $(proper), 94 $broken = $(broken); 95 96 if ( deferred ) { 97 if ( broken.length ) { 98 deferred.reject( $images, $proper, $broken ); 99 } else { 100 deferred.resolve( $images ); 101 } 102 } 103 104 if ( $.isFunction( callback ) ) { 105 callback.call( $this, $images, $proper, $broken ); 106 } 107 } 108 109 function imgLoaded( img, isBroken ) { 110 // don't proceed if BLANK image, or image is already loaded 111 if ( img.src === BLANK || $.inArray( img, loaded ) !== -1 ) { 112 return; 113 } 114 115 // store element in loaded images array 116 loaded.push( img ); 117 118 // keep track of broken and properly loaded images 119 if ( isBroken ) { 120 broken.push( img ); 121 } else { 122 proper.push( img ); 123 } 124 125 // cache image and its state for future calls 126 $.data( img, 'imagesLoaded', { isBroken: isBroken, src: img.src } ); 127 128 // trigger deferred progress method if present 129 if ( hasNotify ) { 130 deferred.notifyWith( $(img), [ isBroken, $images, $(proper), $(broken) ] ); 131 } 132 133 // call doneLoading and clean listeners if all images are loaded 134 if ( $images.length === loaded.length ){ 135 setTimeout( doneLoading ); 136 $images.unbind( '.imagesLoaded' ); 137 } 138 } 139 140 // if no images, trigger immediately 141 if ( !$images.length ) { 142 doneLoading(); 143 } else { 144 $images.bind( 'load.imagesLoaded error.imagesLoaded', function( event ){ 145 // trigger imgLoaded 146 imgLoaded( event.target, event.type === 'error' ); 147 }).each( function( i, el ) { 148 var src = el.src; 149 150 // find out if this image has been already checked for status 151 // if it was, and src has not changed, call imgLoaded on it 152 var cached = $.data( el, 'imagesLoaded' ); 153 if ( cached && cached.src === src ) { 154 imgLoaded( el, cached.isBroken ); 155 return; 156 } 157 158 // if complete is true and browser supports natural sizes, try 159 // to check for image status manually
160 if ( el.complete && el.naturalWidth !== undefined ) { 161 imgLoaded( el, el.naturalWidth === 0 || el.naturalHeight === 0 ); 162 return; 163 } 164 165 // cached images don't fire load sometimes, so we reset src, but only when 166 // dealing with IE, or image is complete (loaded) and failed manual check 167 // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f 168 if ( el.readyState || el.complete ) { 169 el.src = BLANK; 170 el.src = src; 171 } 172 }); 173 } 174 175 return deferred ? deferred.promise( $this ) : $this; 176 }; 177 178 // global 179 var $window = $( window ), 180 Modernizr = window.Modernizr; 181 182 $.Stapel = function( options, element ) { 183 184 this.el = $( element ); 185 this._init( options ); 186 187 }; 188 189 // the options 190 $.Stapel.defaults = { 191 // space between the items 192 gutter : 40, 193 // the rotations degree for the 2nd and 3rd item 194 // (to give a more realistic pile effect) 195 pileAngles : 2, 196 // animation settings for the clicked pile's items 197 pileAnimation : { 198 openSpeed : 400, 199 openEasing : 'ease-in-out', // try this :) 'cubic-bezier(.47,1.34,.9,1.03)', 200 closeSpeed : 400, 201 closeEasing : 'ease-in-out' 202 }, 203 // animation settings for the other piles 204 otherPileAnimation : { 205 openSpeed : 400, 206 openEasing : 'ease-in-out', 207 closeSpeed : 350, 208 closeEasing : 'ease-in-out' 209 }, 210 // delay for each item of the pile 211 delay : 0, 212 // random rotation for the items once opened 213 randomAngle : false, 214 onLoad : function() { return false; }, 215 onBeforeOpen : function( pileName ) { return false; }, 216 onAfterOpen : function( pileName, totalItems ) { return false; }, 217 onBeforeClose : function( pileName ) { return false; }, 218 onAfterClose : function( pileName, totalItems ) { return false; } 219 }; 220 221 $.Stapel.prototype = { 222 223 _init : function( options ) { 224 225 // options 226 this.options = $.extend( true, {}, $.Stapel.defaults, options ); 227 228 // cache some elements 229 this._config(); 230 231 // preload images 232 var self = this; 233 this.el.imagesLoaded( function() { 234 self.options.onLoad(); 235 self._layout(); 236 self._initEvents(); 237 } ); 238 239 }, 240 _config : function() { 241 242 // css transitions support 243 this.support = Modernizr.csstransitions; 244 245 var transEndEventNames = { 246 'WebkitTransition' : 'webkitTransitionEnd', 247 'MozTransition' : 'transitionend', 248 'OTransition' : 'oTransitionEnd', 249 'msTransition' : 'MSTransitionEnd', 250 'transition' : 'transitionend' 251 }, 252 transformNames = { 253 'WebkitTransform' : '-webkit-transform', 254 'MozTransform' : '-moz-transform', 255 'OTransform' : '-o-transform', 256 'msTransform' : '-ms-transform', 257 'transform' : 'transform' 258 }; 259 260 if( this.support ) { 261 262 this.transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ] + '.cbpFWSlider'; 263 this.transformName = transformNames[ Modernizr.prefixed( 'transform' ) ]; 264 265 } 266 267 // true if one pile is opened 268 this.spread = false; 269 270 // the li's 271 this.items = this.el.children( 'li' ).hide(); 272 273 // close pile 274 this.close = $( '#tp-close' ); 275 276 }, 277 _getSize : function() { 278 279 this.elWidth = this.el.outerWidth( true ); 280 281 }, 282 _initEvents : function() { 283 284 var self = this; 285 $window.on( 'debouncedresize.stapel', function() { self._resize(); } ); 286 this.items.on( 'click.stapel', function() { 287 288 var $item = $( this ); 289 290 if( !self.spread && $item.data( 'isPile' ) ) { 291 292 self.spread = true; 293 self.pileName = $item.data( 'pileName' ); 294 self.options.onBeforeOpen( self.pileName ); 295 self._openPile(); 296 297 return false; 298 299 } 300 301 } ); 302 303 }, 304 _layout : function() { 305 306 /* 307 piles() : save the items info in a object with the following structure: 308 309 this.piles = { 310 311 pileName : { 312 313 // elements of this pile (note that an element can be also in a different pile) 314 // for each element, the finalPosition is the position of the element when the pile is opened 315 elements : [ 316 { el : HTMLELEMENT, finalPosition : { left : LEFT, top : TOP } }, 317 {}, 318 {}, 319 ... 320 ], 321 // this is the position of the pile (all elements of the pile) when the pile is closed 322 position : { left : LEFT, top : TOP }, 323 index : INDEX 324 }, 325 326 // more piles 327 ... 328 329 } 330 */ 331 this._piles(); 332 333 // items width & height 334 // assuming here that all items have the same width and height 335 this.itemSize = { width : this.items.outerWidth( true ) , height : this.items.outerHeight( true ) }; 336 337 // remove original elements 338 this.items.remove(); 339 340 // applies some initial style for the items 341 this._setInitialStyle(); 342 343 this.el.css( 'min-width', this.itemSize.width + this.options.gutter ); 344 345 // gets the current ul size (needed for the calculation of each item's position) 346 this._getSize(); 347 348 // calculate and set each Pile's elements position based on the current ul width 349 // this function will also be called on window resize 350 this._setItemsPosition(); 351 352 // new items 353 this.items = this.el.children( 'li' ).show(); 354 // total items 355 this.itemsCount = this.items.length; 356 357 }, 358 _piles : function() { 359 360 this.piles = {}; 361 var pile, self = this, idx = 0; 362 this.items.each( function() { 363 364 var $item = $( this ),
365 itemPile = $item.attr( 'data-pile' ) || 'nopile-' + $item.index(), 366 attr = itemPile.split( ',' ); 367 368 for( var i = 0, total = attr.length; i < total; ++i ) { 369 370 var pileName = $.trim( attr[i] ); 371 pile = self.piles[ pileName ]; 372 373 if( !pile ) { 374 375 pile = self.piles[ pileName ] = { 376 elements : [], 377 position : { left : 0, top : 0 }, 378 index : idx 379 }; 380 381 ++idx; 382 383 } 384 385 var clone = $item.clone().get(0); 386 pile.elements.push( { el : clone, finalPosition : { left : 0, top : 0 } } ); 387 $( clone ).appendTo( self.el ); 388 389 } 390 391 } ); 392 393 }, 394 _setInitialStyle : function() { 395 396 for( var pile in this.piles ) { 397 398 var p = this.piles[pile]; 399 400 for( var i = 0, len = p.elements.length; i < len; ++i ) { 401 402 var $el = $( p.elements[i].el ), 403 styleCSS = { transform : 'rotate(0deg)' }; 404 405 this._applyInitialTransition( $el ); 406 407 if( i === len - 2 ) { 408 styleCSS = { transform : 'rotate(' + this.options.pileAngles + 'deg)' }; 409 } 410 else if( i === len - 3 ) { 411 styleCSS = { transform : 'rotate(-' + this.options.pileAngles + 'deg)' }; 412 } 413 else if( i !== len - 1 ) { 414 var extraStyle = { visibility : 'hidden' }; 415 $el.css( extraStyle ).data( 'extraStyle', extraStyle ); 416 } 417 else if( pile.substr( 0, 6 ) !== 'nopile' ) { 418 $el.data( 'front', true ).append( '<div class="tp-title"><span>' + pile + '</span><span>' + len + '</span></div>' ); 419 } 420 421 $el.css( styleCSS ).data( { 422 initialStyle : styleCSS, 423 pileName : pile, 424 pileCount : len, 425 shadow : $el.css( 'box-shadow' ), 426 isPile : pile.substr( 0, 6 ) === 'nopile' ? false : true 427 } ); 428 429 } 430 431 } 432 433 }, 434 _applyInitialTransition : function( $el ) { 435 436 if( this.support ) { 437 $el.css( 'transition', 'left 400ms ease-in-out, top 400ms ease-in-out' ); 438 } 439 440 }, 441 _setItemsPosition : function() { 442 443 var accumL = 0, accumT = 0, 444 l, t, ml = 0, 445 lastItemTop = 0; 446 447 for( var pile in this.piles ) { 448 449 var p = this.piles[pile], 450 stepW = this.itemSize.width + this.options.gutter, 451 452 accumIL = 0, accumIT = 0, il, it; 453 454 if( accumL + stepW <= this.elWidth ) { 455 456 l = accumL; 457 t = accumT; 458 accumL += stepW; 459 460 } 461 else { 462 463 if( ml === 0 ) { 464 ml = Math.ceil( ( this.elWidth - accumL + this.options.gutter ) / 2 ); 465 } 466 467 accumT += this.itemSize.height + this.options.gutter; 468 l = 0; 469 t = accumT; 470 accumL = stepW; 471 472 } 473 474 p.position.left = l; 475 p.position.top = t; 476
477 for( var i = 0, len = p.elements.length; i < len; ++i ) { 478 479 var elem = p.elements[i], 480 fp = elem.finalPosition; 481 482 if( accumIL + stepW <= this.elWidth ) { 483 484 il = accumIL; 485 it = accumIT; 486 accumIL += stepW; 487 488 } 489 else { 490 491 accumIT += this.itemSize.height + this.options.gutter; 492 il = 0; 493 it = accumIT; 494 accumIL = stepW; 495 496 } 497 498 fp.left = il; 499 fp.top = it; 500 501 var $el = $( elem.el ); 502 503 if( pile !== this.pileName ) { 504 505 $el.css( { left : p.position.left, top : p.position.top } ); 506 507 } 508 else { 509 510 lastItemTop = elem.finalPosition.top; 511 $el.css( { left : elem.finalPosition.left, top : lastItemTop } ); 512 513 } 514 515 } 516 517 } 518 519 // the position of the items will influence the final margin left value and height for the ul 520 // center the ul 521 lastItemTop = this.spread ? lastItemTop : accumT; 522 this.el.css( { 523 marginLeft : ml, 524 height : lastItemTop + this.itemSize.height 525 } ); 526 527 }, 528 _openPile : function() { 529 530 if( !this.spread ) { 531 return false; 532 } 533 534 // final style 535 var fs; 536 for( var pile in this.piles ) { 537 538 var p = this.piles[ pile ], cnt = 0; 539 540 for( var i = 0, len = p.elements.length; i < len; ++i ) { 541 542 var elem = p.elements[i], 543 $item = $( elem.el ), 544 $img = $item.find( 'img' ), 545 styleCSS = pile === this.pileName ? { 546 zIndex : 9999, 547 visibility : 'visible', 548 transition : this.support ? 'left ' + this.options.pileAnimation.openSpeed + 'ms ' + ( ( len - i - 1 ) * this.options.delay ) + 'ms ' + this.options.pileAnimation.openEasing + ', top ' + this.options.pileAnimation.openSpeed + 'ms ' + ( ( len - i - 1 ) * this.options.delay ) + 'ms ' + this.options.pileAnimation.openEasing + ', ' + this.transformName + ' ' + this.options.pileAnimation.openSpeed + 'ms ' + ( ( len - i - 1 ) * this.options.delay ) + 'ms ' + this.options.pileAnimation.openEasing : 'none' 549 } : { 550 zIndex : 1, 551 transition : this.support ? 'opacity ' + this.options.otherPileAnimation.closeSpeed + 'ms ' + this.options.otherPileAnimation.closeEasing : 'none' 552 }; 553 554 if( pile === this.pileName ) { 555 556 if( $item.data( 'front' ) ) { 557 $item.find( 'div.tp-title' ).hide(); 558 } 559 560 if( i < len - 1 ) { 561 $img.css( 'visibility', 'visible' ); 562 } 563 564 fs = elem.finalPosition; 565 fs.transform = this.options.randomAngle && i !== p.index ? 'rotate(' + Math.floor( Math.random() * ( 5 + 5 + 1 ) - 5 ) + 'deg)' : 'none'; 566 567 if( !this.support ) { 568 $item.css( 'transform', 'none' ); 569 } 570 571 // hack: remove box-shadow while animating to prevent the shadow stack effect 572 if( i < len - 3 ) { 573 $item.css( 'box-shadow', 'none' ); 574 } 575 576 } 577 else if( i < len - 1 ) { 578 $img.css( 'visibility', 'hidden' ); 579 } 580 581 $item.css( styleCSS ); 582 583 var self = this; 584 585 pile === this.pileName ? 586 this._applyTransition( $item, fs, this.options.pileAnimation.openSpeed, function( evt ) { 587 588 var target = this.target || this.nodeName; 589 if( target !== 'LI' ) { 590 return; 591 } 592 593 var $el = $( this ); 594 595 // hack: remove box-shadow while animating to prevent the shadow stack effect 596 $el.css( 'box-shadow', $el.data( 'shadow' ) ); 597 598 if( self.support ) { 599 $el.off( self.transEndEventName ); 600 } 601 602 ++cnt; 603 604 if( cnt === $el.data( 'pileCount' ) ) { 605 606 $( document ).one( 'mousemove.stapel', function() { 607 self.el.addClass( 'tp-open' ); 608 } ); 609 self.options.onAfterOpen( self.pileName, cnt ); 610 611 } 612 613 } ) : 614 this._applyTransition( $item, { opacity : 0 }, this.options.otherPileAnimation.closeSpeed ); 615 616 } 617 618 } 619 620 this.el.css( 'height', fs.top + this.itemSize.height ); 621 622 }, 623 _closePile : function() { 624 625 var self = this; 626 627 // close.. 628 if( this.spread ) { 629 630 this.spread = false; 631 632 this.options.onBeforeClose( this.pileName ); 633 634 this.el.removeClass( 'tp-open' ); 635 636 // final style 637 var fs; 638 for( var pile in this.piles ) { 639 640 var p = this.piles[ pile ], cnt = 0; 641 642 for( var i = 0, len = p.elements.length; i < len; ++i ) { 643 644 var $item = $( p.elements[i].el ), 645 styleCSS = pile === this.pileName ? { 646 transition : this.support ? 'left ' + this.options.pileAnimation.closeSpeed + 'ms ' + this.options.pileAnimation.closeEasing
646+ ', top ' + this.options.pileAnimation.closeSpeed + 'ms ' + this.options.pileAnimation.closeEasing + ', ' + this.transformName + ' ' + this.options.pileAnimation.closeSpeed + 'ms ' + this.options.pileAnimation.closeEasing : 'none' 647 } : { 648 transition : this.support ? 'opacity ' + this.options.otherPileAnimation.openSpeed + 'ms ' + this.options.otherPileAnimation.openEasing : 'none' 649 }; 650 651 $item.css( styleCSS ); 652 653 fs = p.position; 654 655 if( pile === this.pileName ) { 656 657 $.extend( fs, $item.data( 'initialStyle' ) ); 658 659 // hack: remove box-shadow while animating to prevent the shadow stack effect 660 if( i < len - 3 ) { 661 $item.css( 'box-shadow', 'none' ); 662 } 663 664 } 665 666 pile === this.pileName ? this._applyTransition( $item, fs, this.options.pileAnimation.closeSpeed, function( evt ) { 667 668 var target = this.target || this.nodeName; 669 if( target !== 'LI' ) { 670 return; 671 } 672 673 var $el = $( this ), extraStyle = $el.data( 'extraStyle' ); 674 675 // hack: remove box-shadow while animating to prevent the shadow stack effect 676 $el.css( 'box-shadow', $el.data( 'shadow' ) ); 677 678 if( self.support ) { 679 $el.off( self.transEndEventName ); 680 self._applyInitialTransition( $el ); 681 } 682 else { 683 $el.css( $el.data( 'initialStyle' ) ); 684 } 685 686 if( extraStyle ) { 687 $el.css( extraStyle ); 688 } 689 690 ++cnt; 691 692 if( $el.data( 'front' ) ) { 693 $el.find( 'div.tp-title' ).show(); 694 } 695 696 if( cnt === $el.data( 'pileCount' ) ) { 697 self.options.onAfterClose( $el.data( 'pileName' ), cnt ); 698 } 699 700 } ) : this._applyTransition( $item, { opacity : 1 }, this.options.otherPileAnimation.openSpeed, function( evt ) { 701 702 var target = this.target || this.nodeName; 703 if( target !== 'LI' ) { 704 return; 705 } 706 707 var $el = $( this ); 708 709 if( $el.index() < len - 1 ) { 710 $el.find( 'img' ).css( 'visibility', 'visible' ); 711 } 712 713 if( self.support ) { 714 $el.off( self.transEndEventName ); 715 self._applyInitialTransition( $el ); 716 } 717 718 } ); 719 720 } 721 722 } 723 724 // reset pile name 725 this.pileName = ''; 726 727 // update ul height 728 this.el.css( 'height', fs.top + this.itemSize.height ); 729 730 } 731 732 return false; 733 734 }, 735 _resize : function() { 736 737 // get ul size again 738 this._getSize(); 739 // reset items positions 740 this._setItemsPosition(); 741 742 }, 743 _applyTransition : function( el, styleCSS, speed, fncomplete ) { 744 745 $.fn.applyStyle = this.support ? $.fn.css : $.fn.animate; 746 747 if( fncomplete && this.support ) { 748 749 el.on( this.transEndEventName, fncomplete ); 750 751 } 752 753 fncomplete = fncomplete || function() { return false; }; 754 755 el.stop().applyStyle( styleCSS, $.extend( true, [], { duration : speed + 'ms', complete : fncomplete } ) ); 756 757 }, 758 closePile : function() { 759 760 this._closePile(); 761 762 } 763 764 }; 765 766 var logError = function( message ) { 767 768 if ( window.console ) { 769 770 window.console.error( message ); 771 772 } 773 774 }; 775 776 $.fn.stapel = function( options ) { 777 778 var instance = $.data( this, 'stapel' ); 779 780 if ( typeof options === 'string' ) { 781 782 var args = Array.prototype.slice.call( arguments, 1 ); 783 784 this.each(function() { 785 786 if ( !instance ) { 787 788 logError( "cannot call methods on stapel prior to initialization; " + 789 "attempted to call method '" + options + "'" ); 790 return; 791 792 } 793 794 if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { 795 796 logError( "no such method '" + options + "' for stapel instance" ); 797 return; 798 799 } 800 801 instance[ options ].apply( instance, args ); 802 803 }); 804 805 } 806 else { 807 808 this.each(function() { 809 810 if ( instance ) { 811 812 instance._init(); 813 814 } 815 else { 816 817 instance = $.data( this, 'stapel', new $.Stapel( options, this ) ); 818 819 } 820 821 }); 822 823 } 824 825 return instance; 826 827 }; 828 829} )( jQuery, window );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.