PageSourceSearch

https://group-anons.ru/js/jquery.stapel.js

js group-anons.ru collected 2026-10-04 01:44:32 UTC 20,552 bytes, 829 lines download raw bytes

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.