PageSourceSearch

https://samopal.net/public/js/carousel.js

js samopal.net collected 2026-09-25 08:53:22 UTC 16,166 bytes, 550 lines download raw bytes

1/*! (c) Mat Marquis (@wilto). MIT License. http://wil.to/3a */
2
3(function( $, undefined ) {
4	var inst = 0;
5
6	$.fn.getPercentage = function() {
7		var oPercent = this.attr('style').match(/margin\-left:(.*[0-9])/i) && parseInt(RegExp.$1);
8
9		return oPercent;
10	};
11
12	$.fn.adjRounding = function(slide) {
13		var $el = $(this),
14			$slides = $el.find( slide ),
15			diff = $el.parent().width() - $($slides[0]).width();
16
17		if (diff !== 0) { 
18			$($slides).css( "position", "relative" );
19
20			for (var i = 0; i < $slides.length; i++) {
21				$($slides[i]).css( "left", (diff * i) + "px" );
22			}
23		}
24
25		return this;
26	};
27
28	$.fn.carousel = function(config) {
29
30		// Prevent re-init:
31		if( this.data( "carousel-initialized" ) ) { return; }
32
33		// Carousel is being initialized:
34		this.data( "carousel-initialized", true );
35
36		var defaults = {
37			slider			: '.slider',
38			slide			: '.slide',
39			prevSlide		: null,
40			nextSlide		: null,
41			slideHed		: null,
42			addPagination	: false,
43			addNav			: ( config != undefined && ( config.prevSlide || config.nextSlide ) ) ? false : true,
44			namespace		: 'carousel',
45			speed			: 300
46		},
47		opt               = $.extend(defaults, config),
48		$slidewrap        = this,
49		dBody            = (document.body || document.documentElement),
50		transitionSupport = function() {
51			// Original code from Modernizr : http://www.modernizr.com/
52		    var prop = 'transitionProperty',
53				modElem = document.createElement('tester'),
54				mStyle = modElem.style,
55				prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '),
56				domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
57				ucProp  = prop.charAt(0).toUpperCase() + prop.substr(1),
58				props   = (prop + ' ' + domPrefixes.join(ucProp + ' ') + ucProp).split(' ');
59
60			for ( var i in props ) {
61				if ( mStyle[ props[i] ] !== undefined ) {
62					return true;
63				}
64			}
65			return false;
66		},
67		carousel = {
68			init : function() {				
69				inst++;
70
71				$slidewrap.each(function(carInt) {
72						var $wrap      = $(this),
73							$slider    = $wrap.find(opt.slider),
74							$slide     = $wrap.find(opt.slide),			
75							slidenum   = $slide.length,
76							transition = "margin-left " + ( opt.speed / 1000 ) + "s ease",
77							tmp        = 'carousel-' + inst + '-' + carInt;
78
79						if( $slide.length <= 1 ) {
80							return; /* No sense running all this code if the carousel functionality is unnecessary. */
81						}
82
83						$wrap
84							.css({
85								overflow             : "hidden",
86								width                : "100.2%"
87							})
88							.attr('role' , 'application');
89
90						$slider
91							.attr( 'id', ( $slider[0].id || 'carousel-' + inst + '-' + carInt ) )
92							.css({
93								"marginLeft"         : "0px",
94								"float"              : "left",
95								"width"              : 100 * slidenum + "%",
96								"-webkit-transition" : transition,
97								"-moz-transition"    : transition,
98								"-ms-transition"     : transition,
99								"-o-transition"      : transition,
100								"transition"         : transition
101							})
102							.bind( 'carouselmove' , carousel.move )
103							.bind( 'nextprev'     , carousel.nextPrev )
104							.bind( 'navstate'     , carousel.navState );
105
106						$slide
107							.css({
108								"float": "left",
109								width: (100 / slidenum) + "%"				
110							})
111							.each(function(i) {
112								var $el = $(this);
113
114								$el.attr({
115									role : "tabpanel document",
116									id   : tmp + '-slide' + i
117								});
118
119								if( opt.addPagination ) {
120									$el.attr('aria-labelledby', tmp + '-tab' + i);
121								}
122							});
123
124						// Build and insert navigation/pagination, if specified in the options:
125						opt.addPagination   && carousel.addPagination();
126						opt.addNav 			&& carousel.addNav();
127
128						$slider.trigger( "navstate", { current: 0 });
129				});
130			},
131			addNav : function() {
132				$slidewrap.each(function(i) {						
133					var $oEl = $(this),
134						$slider = $oEl.find(opt.slider),
135						currentSlider = $slider[0].id,
136						navMarkup = [
137							'<ul class="slidecontrols" role="navigation">',
138							'	<li role="presentation"><a href="#' + currentSlider + '" class="' + opt.namespace + '-next">Next</a></li>',
139							'	<li role="presentation"><a href="#' + currentSlider + '" class="' + opt.namespace + '-prev">Prev</a></li>',
140							'</ul>'
141							].join(''),
142						nextprev = {
143							nextSlide : '.' + opt.namespace + '-next',
144							prevSlide : '.' + opt.namespace + '-prev'
145						};
146
147					opt = $.extend(opt, nextprev);
148
149					$oEl.prepend(navMarkup);
150				});
151			},
152			addPagination : function() {
153				$slidewrap.each(function(i) {
154					var $oEl        = $(this),
155						$pagination = $('<ol class="' + opt.namespace + '-tabs" role="tablist navigation" />'),
156						$slider     = $oEl.find(opt.slider),
157						$slides     = $oEl.find(opt.slide)
158						slideNum    = $slides.length,
159						associated  = 'carousel-' + inst + '-' + i;
160
161					while( slideNum-- ) {
162						var hed = $( $slides[ slideNum ] ).find( opt.slideHed ).text() || 'Page ' + ( slideNum + 1 ),
163							tabMarkup = [
164								'<li role="presentation">',
165									'<a href="#' + associated + '-slide' + slideNum +'"',
166									' aria-controls="' + associated + '-slide' + slideNum +'"',
167									' id="' + associated + '-tab' + slideNum + '" role="tab">' + hed + '</a>',
168								'</li>'
169							].join('');
170
171						$pagination.prepend(tabMarkup);
172					};
173
174					$pagination
175						.appendTo( $oEl )
176						.find('li').keydown( function(e) {
177							var $el      = $(this),
178								$prevTab = $el.prev().find('a'),
179								$nextTab = $el.next().find('a');
180
181							switch( e.which ) {
182								case 37:
183								case 38:		
184									$prevTab.length && $prevTab.trigger('click').focus();
185									e.preventDefault();
186									break;
187								case 39: 
188								case 40:
189									$nextTab.length && $nextTab.trigger('click').focus();
190									e.preventDefault();
191									break;
192							}
193						})
194						.find('a').click( function(e) {
195							var $el = $(this);
196
197							if( $el.attr('aria-selected') == 'false' ) { 
198								var current = $el.parent().index(),
199									move    = -( 100 * ( current ) ),
200									$slider = $oEl.find( opt.slider );
201
202								$slider.trigger( 'carouselmove', { moveTo: move });
203							}
204							e.preventDefault();
205						});
206				});
207			},
208			roundDown : function(oVal) {
209				var val = parseInt(oVal, 10);
210
211				return Math.ceil( (val - (val % 100 ) ) / 100) * 100;
212			},
213			navState : function(e, ui) {
214				var $el          = $(this),
215					$slides      = $el.find(opt.slide),
216					ind          = -(ui.current / 100),
217					$activeSlide = $($slides[ind]);
218
219				$el.attr('aria-activedescendant', $activeSlide[0].id);
220
221				// Update state of active tabpanel:
222				$activeSlide
223					.addClass( opt.namespace + "-active-slide" )
224					.attr( 'aria-hidden', false )
225					.siblings()	
226						.removeClass( opt.namespace + "-active-slide" )
227						.attr( 'aria-hidden', true );
228
229				// Update state of next/prev navigation:
230				if( ( !!opt.prevSlide || !!opt.nextSlide ) ) {
231					var $target = $('[href*="#' + this.id + '"]');
232
233					$target.removeClass( opt.namespace + '-disabled' );
234
235					if( ind == 0 ) {
236						$target.filter(opt.prevSlide).addClass( opt.namespace + '-disabled' );
237					} else if( ind == $slides.length - 1 ) {
238						$target.filter(opt.nextSlide).addClass( opt.namespace + '-disabled' );
239					}
240				}
241
242				// Update state of pagination tabs:
243				if( !!opt.addPagination ) {
244					var tabId = $activeSlide.attr('aria-labelledby'),
245						$tab  = $('#' + tabId );
246
247					$tab
248						.parent()
249						.addClass(opt.namespace + '-active-tab')
250						.siblings()
251						.removeClass(opt.namespace + '-active-tab')
252						.find('a')
253							.attr({
254								'aria-selected' : false,
255								'tabindex' : -1
256							});
257
258					$tab.attr({
259						'aria-selected' : true,
260						'tabindex' : 0
261					});
262				}
263			},
264			move : function(e, ui) {
265				var $el = $(this);
266
267				$el
268					.trigger(opt.namespace + "-beforemove")
269					.trigger("navstate", { current: ui.moveTo });
270
271				if( transitionSupport() ) {
272
273					$el
274						.adjRounding( opt.slide ) /* Accounts for browser rounding errors. Lookin’ at you, iOS Safari. */
275						.css('marginLeft', ui.moveTo + "%")
276						.one("transitionend webkitTransitionEnd OTransitionEnd", function() {
277							$(this).trigger( opt.namespace + "-aftermove" );
278						});
279
280				} else {					
281					$el
282						.adjRounding( opt.slide )
283						.animate({ marginLeft: ui.moveTo + "%" }, { duration : opt.speed, queue : false }, function() {
284							$(this).trigger( opt.namespace + "-aftermove" );
285						});
286				}
287			},
288			nextPrev : function(e, ui) {				
289				var $el = $(this),
290					left = ( $el ) ? $el.getPercentage() : 0,
291					$slide = $el.find(opt.slide),
292					constrain = ui.dir === 'prev' ? left != 0 : -left < ($slide.length - 1) * 100,
293					$target = $( '[href="#' + this.id + '"]');
294
295				if (!$el.is(":animated") && constrain ) {
296
297					if ( ui.dir === 'prev' ) {
298						left = ( left % 100 != 0 ) ? carousel.roundDown(left) : left + 100;
299					} else {
300						left = ( ( left % 100 ) != 0 ) ? carousel.roundDown(left) - 100 : left - 100;
301					}
302
303					$el.trigger('carouselmove', { moveTo: left });
304
305					$target
306						.removeClass( opt.namespace + '-disabled')
307						.removeAttr('aria-disabled');
308
309					switch( left ) {
310						case ( -($slide.length - 1) * 100 ):
311							$target.filter(opt.nextSlide)
312								.addClass( opt.namespace + '-disabled')
313								.attr('aria-disabled', true);
314							break;
315						case 0:
316							$target.filter(opt.prevSlide)
317								.addClass( opt.namespace + '-disabled')
318								.attr('aria-disabled', true);
319							break;
320					}
321				} else {
322					var reset = carousel.roundDown(left);
323
324					$el.trigger('carouselmove', { moveTo: reset });
325				}
326
327			}
328		};
329
330		carousel.init(this);
331
332		$(opt.nextSlide + ',' + opt.prevSlide)
333			.bind('click', function(e) {				
334				var $el = $(this),
335					link = this.hash,
336					dir = ( $el.is(opt.prevSlide) ) ? 'prev' : 'next',
337					$slider = $(link);
338
339					if ( $el.is('.' + opt.namespace + '-disabled') ) { 
340						return false;
341					}
342
343					$slider.trigger('nextprev', { dir: dir });
344
345				e.preventDefault();
346			})
347			.bind('keydown', function(e) {
348				var $el = $(this),
349					link = this.hash;
350
351				switch (e.which) {
352					case 37:
353					case 38:
354						$('#' + link).trigger('nextprev', { dir: 'next' });
355						e.preventDefault();
356						break;
357					case 39:
358					case 40:
359						$('#' + link).trigger('nextprev', { dir: 'prev' });
360						e.preventDefault();
361						break;
362				}
363			});
364
365		var setup = {
366			wrap : this,
367			slider : opt.slider
368		};
369		$slidewrap.bind( "dragSnap", setup, function(e, ui){
370			var $slider = $(this).find( opt.slider ),
371				dir = ( ui.direction === "left" ) ? 'next' : 'prev';
372
373			$slider.trigger("nextprev", { dir: dir });	
374		});
375
376
377		$slidewrap.filter('[data-autorotate]').each(function() {
378			var auto,
379				$el         = $(this),
380				speed       = $el.attr('data-autorotate'),
381				slidenum    = $el.find(opt.slide).length,
382				autoAdvance = function() {
383					var $slider  = $el.find(opt.slider),
384						active   = -( $(opt.slider).getPercentage() / 100 ) + 1;
385
386					switch( active ) {
387						case slidenum: 
388							clearInterval(auto);
389
390							auto = setInterval(function() {
391								autoAdvance();
392								$slider.trigger("nextprev", { dir: 'prev' });	
393							}, speed);
394
395							break;
396						case 1:
397							clearInterval(auto);
398
399							auto = setInterval(function() {
400								autoAdvance();								
401								$slider.trigger("nextprev", { dir: 'next' });	
402							}, speed);
403
404							break;
405					}
406				};
407
408			auto = setInterval(autoAdvance, speed);
409
410			$el
411				.attr('aria-live', 'polite')
412				.bind('mouseenter click touchstart', function() {
413					clearInterval(auto);
414				});
415		});
416
417		return this;
418	};
419})(jQuery);
420
421
422$.event.special.dragSnap = {
423	setup: function(setup) {
424
425		var $el = $(this),
426			transitionSwap = function($el, tog) {
427				var speed = .3,
428					transition = ( tog ) ? "margin-left " + speed + "s ease" : 'none';
429
430				$el.css({
431					"-webkit-transition" : transition,
432					"-moz-transition"    : transition,
433					"-ms-transition"     : transition,
434					"-o-transition"      : transition,
435					"transition"         : transition
436				});
437			},
438			roundDown = function(left) {
439				var left = parseInt(left, 10);
440
441				return Math.ceil( (left - (left % 100 ) ) / 100) * 100;
442			},
443			snapBack = function(e, ui) {
444				var $el = ui.target,
445					currentPos = ( $el.attr('style') != undefined ) ? $el.getPercentage() : 0,
446					left = (ui.left === false) ? roundDown(currentPos) - 100 : roundDown(currentPos),
447					dStyle = document.body.style,
448					transitionSupport = function() {
449						// Original code from Modernizr : http://www.modernizr.com/
450					    var prop = 'transitionProperty',
451							modElem = document.createElement('tester'),
452							mStyle = modElem.style,
453							prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '),
454							domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
455							ucProp  = prop.charAt(0).toUpperCase() + prop.substr(1),
456							props   = (prop + ' ' + domPrefixes.join(ucProp + ' ') + ucProp).split(' ');
457
458						for ( var i in props ) {
459							if ( mStyle[ props[i] ] !== undefined ) {
460								return true;
461							}
462						}
463						return false;
464					};
465
466				transitionSwap($el, true);
467
468				if( transitionSupport() ) {
469					$el.css('marginLeft', left + "%");
470				} else {
471					$el.animate({ marginLeft: left + "%" }, opt.speed);
472				}
473			};
474
475		$el
476			.bind("snapback", snapBack)
477			.bind("touchstart", function(e) {
478				var data = e.originalEvent.touches ? e.originalEvent.touches[0] : e,
479					start = {
480						time: (new Date).getTime(),
481						coords: [ data.pageX, data.pageY ],
482						origin: $(e.target).closest( setup.wrap )
483					},
484					stop,
485					$tEl = $(e.target).closest( setup.slider ),
486					currentPos = ( $tEl.attr('style') != undefined ) ? $tEl.getPercentage() : 0;
487
488				transitionSwap($tEl, false);
489
490				function moveHandler(e) {
491					var data = e.originalEvent.touches ? e.originalEvent.touches[0] : e;
492					stop = {
493							time: (new Date).getTime(),
494							coords: [ data.pageX, data.pageY ]
495					};
496
497					if(!start || Math.abs(start.coords[0] - stop.coords[0]) < Math.abs(start.coords[1] - stop.coords[1]) ) {
498						return;
499					}
500
501					$tEl.css({"margin-left": currentPos + ( ( (stop.coords[0] - start.coords[0]) / start.origin.width() ) * 100 ) + '%' });						
502
503					// prevent scrolling
504					if (Math.abs(start.coords[0] - stop.coords[0]) > 10) {
505						e.preventDefault();
506					}
507
508				};
509
510				$el
511					.bind("gesturestart", function(e) {
512						$el
513							.unbind("touchmove", moveHandler)
514							.unbind("touchend", moveHandler);
515					})
516					.bind("touchmove", moveHandler)
517					.one("touchend", function(e) {
518
519						$el.unbind("touchmove", moveHandler);
520
521						transitionSwap($tEl, true);
522
523						if (start && stop ) {
524
525							if (Math.abs(start.coords[0] - stop.coords[0]) > 10
526								&& Math.abs(start.coords[0] - stop.coords[0]) > Math.abs(start.coords[1] - stop.coords[1])) {
527								e.preventDefault();
528							} else {
529								$el.trigger('snapback', { target: $tEl, left: true });
530								return;
531							}
532
533							if (Math.abs(start.coords[0] - stop.coords[0]) > 1 && Math.abs(start.coords[1] - stop.coords[1]) < 75) {
534								var left = start.coords[0] > stop.coords[0];
535
536							if( -( stop.coords[0] - start.coords[0]) > ( start.origin.width() / 4 ) || ( stop.coords[0] - start.coords[0]) > ( start.origin.width() / 4 ) ) {
537
538								start.origin.trigger("dragSnap", {direction: left ? "left" : "right"});
539
540								} else {								
541									$el.trigger('snapback', { target: $tEl, left: left });
542								}
543
544							}
545						}
546						start = stop = undefined;
547					});
548			});
549	}
550};

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.