PageSourceSearch

https://www.lennusport.org/jscripts/unslider202/unslider.js

js lennusport.org collected 2026-10-03 17:57:40 UTC 18,110 bytes, 630 lines download raw bytes

1/**
2 *   Unslider
3 *   version 2.0
4 *   by @idiot and friends
5 */
6 
7(function($) {
8	//  Don't throw any errors when jQuery
9	if(!$) {
10		return console.warn('Unslider needs jQuery');
11	}
12
13	$.Unslider = function(context, options) {
14		var self = this;
15
16		//  Create an Unslider reference we can use everywhere
17		self._ = 'unslider';
18
19		//  Store our default options in here
20		//  Everything will be overwritten by the jQuery plugin though
21		self.defaults = {
22			//  Should the slider move on its own or only when
23			//  you interact with the nav/arrows?
24			//  Only accepts boolean true/false.
25			autoplay: false,
26
27			//  3 second delay between slides moving, pass
28			//  as a number in milliseconds.
29			delay: 3000,
30
31			//  Animation speed in millseconds
32			speed: 750,
33
34			//  An easing string to use. If you're using Velocity, use a
35			//  Velocity string otherwise you can use jQuery/jQ UI options.
36			easing: 'swing', // [.42, 0, .58, 1],
37			
38			//  Does it support keyboard arrows?
39			//  Can pass either true or false -
40			//  or an object with the keycodes, like so:
41			//  {
42			//	 prev: 37,
43			//	 next: 39
44			// }
45			//  You can call any internal method name
46			//  before the keycode and it'll be called.
47			keys: {
48				prev: 37,
49				next: 39
50			},
51			
52			//  Do you want to generate clickable navigation
53			//  to skip to each slide? Accepts boolean true/false or
54			//  a callback function per item to generate.
55			nav: true,
56
57			//  Should there be left/right arrows to go back/forth?
58			//   -> This isn't keyboard support.
59			//  Either set true/false, or an object with the HTML
60			//  elements for each arrow like below:
61			arrows: {
62				prev: '<a class="' + self._ + '-arrow prev">←</a>',
63				next: '<a class="' + self._ + '-arrow next">→</a>'
64			},
65
66			//  How should Unslider animate?
67			//  It can do one of the following types:
68			//  "fade": each slide fades in to each other
69			//  "horizontal": each slide moves from left to right
70			//  "vertical": each slide moves from top to bottom
71			animation: 'horizontal',
72
73			//  If you don't want to use a list to display your slides,
74			//  you can change it here. Not recommended and you'll need
75			//  to adjust the CSS accordingly.
76			selectors: {
77				container: 'ul:first',
78				slides: 'li'
79			},
80
81			//  Do you want to animate the heights of each slide as
82			//  it moves
83			animateHeight: false,
84
85			//  Active class for the nav
86			activeClass: self._ + '-active',
87
88			//  Have swipe support?
89			//  You can set this here with a boolean and always use
90			//  initSwipe/destroySwipe later on.
91			swipe: true
92		};
93
94		//  Set defaults
95		self.$context = context;
96		self.options = {};
97
98		//  Leave our elements blank for now
99		//  Since they get changed by the options, we'll need to
100		//  set them in the init method.
101		self.$parent = null;
102		self.$container = null;
103		self.$slides = null;
104		self.$nav = null;
105		self.$arrows = [];
106		
107		//  Set our indexes and totals
108		self.total = 0;
109		self.current = 0;
110
111		//  Generate a specific random ID so we don't dupe events
112		self.prefix = self._ + '-';
113		self.eventSuffix = '.' + self.prefix + ~~(Math.random() * 2e3);
114
115		//  In case we're going to use the autoplay
116		self.interval = null;
117
118		//  Get everything set up innit
119		self.init = function(options) {
120			//  Set up our options inside here so we can re-init at
121			//  any time
122			self.options = $.extend({}, self.defaults, options);
123
124			//  Our elements
125			self.$container = self.$context.find(self.options.selectors.container).addClass(self.prefix + 'wrap');
126			self.$slides = self.$container.children(self.options.selectors.slides);
127
128			//  We'll manually init the container
129			self.setup();
130
131			//  We want to keep this script as small as possible
132			//  so we'll optimise some checks
133			['nav', 'arrows', 'keys', 'infinite'].forEach(function(module) {
134				self.options[module] && self['init' + $._ucfirst(module)]();
135			});
136
137			//  Add swipe support
138			if(typeof jQuery.event.special.swipe !== undefined && self.options.swipe) {
139				self.initSwipe();
140			}
141
142			//  If autoplay is set to true, call self.start()
143			//  to start calling our timeouts
144			self.options.autoplay && self.start();
145
146			//  We should be able to recalculate slides at will
147			self.calculateSlides();
148
149			//  Listen to a ready event
150			self.$context.trigger(self._ + '.ready');
151
152			//  Everyday I'm chainin'
153			return self.animate(self.options.index || self.current, 'init');
154		};
155
156		self.setup = function() {
157			//  Add a CSS hook to the main element
158			self.$context.addClass(self.prefix + self.options.animation).wrap('<div class="' + self._ + '" />');
159			self.$parent = self.$context.parent('.' + self._);
160
161			//  We need to manually check if the container is absolutely
162			//  or relatively positioned
163			var position = self.$context.css('position');
164
165			//  If we don't already have a position set, we'll
166			//  automatically set it ourselves
167			if(position === 'static') {
168				self.$context.css('position', 'relative');
169			}
170
171			self.$context.css('overflow', 'hidden');
172		};
173
174		//  Set up the slide widths to animate with
175		//  so the box doesn't float over
176		self.calculateSlides = function() {
177			self.total = self.$slides.length;
178
179			//  Set the total width
180			if(self.options.animation !== 'fade') {
181				var prop = 'width';
182
183				if(self.options.animation === 'vertical') {
184					prop = 'height';
185				}
186
187				self.$container.css(prop, (self.total * 100) + '%').addClass(self.prefix + 'carousel');
188				self.$slides.css(prop, (100 / self.total) + '%');
189			}
190		};
191
192
193		//  Start our autoplay
194		self.start = function() {
195			self.interval = setTimeout(function() {
196				//  Move on to the next slide
197				self.next();
198
199				//  If we've got autoplay set up
200				//  we don't need to keep starting
201				//  the slider from within our timeout
202				//  as .animate() calls it for us
203			}, self.options.delay);
204
205			return self;
206		};
207
208		//  And pause our timeouts
209		//  and force stop the slider if needed
210		self.stop = function() {
211			clearTimeout(self.interval);
212
213			return self;
214		};
215
216
217		//  Set up our navigation
218		self.initNav = function() {
219			var $nav = $('<nav class="' + self.prefix + 'nav"><ol /></nav>');
220
221			//  Build our click navigation item-by-item
222			self.$slides.each(function(key) {
223				//  If we've already set a label, let's use that
224				//  instead of generating one
225				var label = this.getAttribute('data-nav') || key + 1;
226
227				//  Listen to any callback functions
228				if($.isFunction(self.options.nav)) {
229					label = self.options.nav.call(self.$slides.eq(key), key, label);
230				}
231
232				//  And add it to our navigation item
233				$nav.children('ol').append('<li data-slide="' + key + '">' + label + '</li>');
234			});
235			
236			//  Keep a copy of the nav everywhere so we can use it
237			self.$nav = $nav.insertAfter(self.$context);
238
239			//  Now our nav is built, let's add it to the slider and bind
240			//  for any click events on the generated links
241			self.$nav.find('li').on('click' + self.eventSuffix, function() {
242				//  Cache our link and set it to be active
243				var $me = $(this).addClass(self.options.activeClass);
244
245				//  Set the right active class, remove any other ones
246				$me.siblings().removeClass(self.options.activeClass);
247
248				//  Move the slide
249				self.animate($me.attr('data-slide'));
250			});
251		};
252
253
254		//  Set up our left-right arrow navigation
255		//  (Not keyboard arrows, prev/next buttons)
256		self.initArrows = function() {
257			if(self.options.arrows === true) {
258				self.options.arrows = self.defaults.arrows;
259			}
260
261			//  Loop our options object and bind our events
262			$.each(self.options.arrows, function(key, val) {
263				//  Add our arrow HTML and bind it
264				self.$arrows.push(
265					$(val).insertAfter(self.$context).on('click' + self.eventSuffix, self[key])
266				);
267			});
268		};
269
270
271		//  Set up our keyboad navigation
272		//  Allow binding to multiple keycodes
273		self.initKeys = function() {
274			if(self.options.keys === true) {
275				self.options.keys = self.defaults.keys;
276			}
277
278			$(document).on('keyup' + self.eventSuffix, function(e) {
279				$.each(self.options.keys, function(key, val) {
280					if(e.which === val) {
281						$.isFunction(self[key]) && self[key].call(self);
282					}
283				});
284			});
285		};
286
287		//  Requires jQuery.event.swipe
288		//  -> stephband.info/jquery.event.swipe
289		self.initSwipe = function() {
290			var width = self.$slides.width();
291
292			self.$container.on({
293				swipeleft: self.next,
294				swiperight: self.prev,
295
296				movestart: function(e) {
297					//  If the movestart heads off in a upwards or downwards
298					//  direction, prevent it so that the browser scrolls normally.
299					if((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) {
300						return !!e.preventDefault();
301					}
302
303					self.$container.css('position', 'relative');
304				}
305			});
306
307			//  We don't want to have a tactile swipe in the slider
308			//  in the fade animation, as it can cause some problems
309			//  with layout, so we'll just disable it.
310			if(self.options.animation !== 'fade') {
311				self.$container.on({
312					move: function(e) {
313						self.$container.css('left', -(100 * self.current) + (100 * e.distX / width) + '%');
314					},
315
316					moveend: function(e) {
317						if((Math.abs(e.distX) / width) < $.event.special.swipe.settings.threshold) {
318							return self._move(self.$container, {left: -(100 * self.current) + '%'}, false, 200);
319						}
320					}
321				});
322			}
323		};
324
325		//  Infinite scrolling is a massive pain in the arse
326		//  so we need to create a whole bloody function to set
327		//  it up. Argh.
328		self.initInfinite = function() {
329			var pos = ['first', 'last'];
330
331			pos.forEach(function(item, index) {
332				self.$slides.push.apply(
333					self.$slides,
334					
335					//  Exclude all cloned slides and call .first() or .last()
336					//  depending on what `item` is.
337					self.$slides.filter(':not(".' + self._ + '-clone")')[item]()
338
339					//  Make a copy of it and identify it as a clone
340					.clone().addClass(self._ + '-clone')
341
342					//  Either insert before or after depending on whether we're
343					//  the first or last clone
344					['insert' + (index === 0 ? 'After' : 'Before')](
345						//  Return the other element in the position array
346						//  if item = first, return "last"
347						self.$slides[pos[~~!index]]()
348					)
349				);
350			});
351		};
352
353		//  Remove any trace of arrows
354		//  Loop our array of arrows and use jQuery to remove
355		//  It'll unbind any event handlers for us
356		self.destroyArrows = function() {
357			self.$arrows.forEach(function($arrow) {
358				$arrow.remove();
359			});
360		};
361
362		//  Remove any swipe events and reset the position
363		self.destroySwipe = function() {
364			//  We bind to 4 events, so we'll unbind those
365			self.$container.off('movestart move moveend');
366		};
367
368		//  Unset the keyboard navigation
369		//  Remove the handler 
370		self.destroyKeys = function() {
371			//  Remove the event handler
372			$(document).off('keyup' + self.eventSuffix);
373		};
374
375		self.setIndex = function(to) {
376			if(to < 0) {
377				to = self.total - 1;
378			}
379
380			self.current = Math.min(Math.max(0, to), self.total - 1);
381
382			if(self.options.nav) {
383				self.$nav.find('[data-slide="' + self.current + '"]')._toggleActive(self.options.activeClass);
384			}
385
386			self.$slides.eq(self.current)._toggleActive(self.options.activeClass);
387
388			return self;
389		};
390		
391		//  Despite the name, this doesn't do any animation - since there's
392		//  now three different types of animation, we let this method delegate
393		//  to the right type, keeping the name for backwards compat.
394		self.animate = function(to, dir) {
395			//  Animation shortcuts
396			//  Instead of passing a number index, we can now
397			//  use .data('unslider').animate('last');
398			//  or .unslider('animate:last')
399			//  to go to the very last slide
400			if(to === 'first') to = 0;
401			if(to === 'last') to = self.total;
402
403			//  Don't animate if it's not a valid index
404			if(isNaN(to)) {
405				return self;
406			}
407
408			if(self.options.autoplay) {
409				self.stop().start();
410			}
411
412			self.setIndex(to);
413
414			//  Add a callback method to do stuff with
415			self.$context.trigger(self._ + '.change', [to, self.$slides.eq(to)]);
416
417			//  Delegate the right method - everything's named consistently
418			//  so we can assume it'll be called "animate" + 
419			var fn = 'animate' + $._ucfirst(self.options.animation);
420
421			//  Make sure it's a valid animation method, otherwise we'll get
422			//  a load of bug reports that'll be really hard to report
423			if($.isFunction(self[fn])) {
424				self[fn](self.current, dir);
425			}
426
427			return self;
428		};
429
430
431		//  Shortcuts for animating if we don't know what the current
432		//  index is (i.e back/forward)
433		//  For moving forward we need to make sure we don't overshoot.
434		self.next = function() {
435			var target = self.current + 1;
436
437			//  If we're at the end, we need to move back to the start
438			if(target >= self.total) {
439				target = 0;
440			}
441
442			return self.animate(target, 'next');
443		};
444
445		//  Previous is a bit simpler, we can just decrease the index
446		//  by one and check if it's over 0.
447		self.prev = function() {
448			return self.animate(self.current - 1, 'prev');
449		};
450		
451
452		//  Our default animation method, the old-school left-to-right
453		//  horizontal animation
454		self.animateHorizontal = function(to) {
455			var prop = 'left';
456
457			//  Add RTL support, slide the slider
458			//  the other way if the site is right-to-left
459			if(self.$context.attr('dir') === 'rtl') {
460				prop = 'right';
461			}
462
463			if(self.options.infinite) {
464				//  So then we need to hide the first slide
465				self.$container.css('margin-' + prop, '-100%');
466			}
467
468			return self.slide(prop, to);
469		};
470
471		//  The same animation methods, but vertical support
472		//  RTL doesn't affect the vertical direction so we
473		//  can just call as is
474		self.animateVertical = function(to) {
475			self.options.animateHeight = true;
476
477			//  Normal infinite CSS fix doesn't work for
478			//  vertical animation so we need to manually set it
479			//  with pixels. Ah well.
480			if(self.options.infinite) {
481				self.$container.css('margin-top', -self.$slides.outerHeight());
482			}
483
484			return self.slide('top', to);
485		}
486
487		//  Actually move the slide now
488		//  We have to pass a property to animate as there's
489		//  a few different directions it can now move, but it's
490		//  otherwise unchanged from before.
491		self.slide = function(prop, to) {
492			//  If we want to change the height of the slider
493			//  to match the current slide, you can set
494			//  {animateHeight: true}
495			if(self.options.animateHeight) {
496				self._move(self.$context, {height: self.$slides.eq(to).outerHeight()}, false);
497			}
498
499			//  For infinite sliding we add a dummy slide at the end and start
500			//  of each slider to give the appearance of being infinite
501			if(self.options.infinite) {
502				var dummy;
503
504				//  Going backwards to last slide
505				if(to === self.total - 1) {
506					//  We're setting a dummy position and an actual one
507					//  the dummy is what the index looks like
508					//  (and what we'll silently update to afterwards),
509					//  and the actual is what makes it not go backwards
510					dummy = self.total - 3;
511					to = -1;
512				}
513
514				//  Going forwards to first slide
515				if(to === self.total - 2) {
516					dummy = 0;
517					to = self.total - 2;
518				}
519
520				//  If it's a number we can safely set it
521				if(typeof dummy === 'number') {
522					self.setIndex(dummy);
523
524					//  Listen for when the slide's finished transitioning so
525					//  we can silently move it into the right place and clear
526					//  this whole mess up.
527					self.$context.on(self._ + '.moved', function() {
528						if(self.current === dummy) {
529							self.$container.css(prop, -(100 * dummy) + '%').off(self._ + '.moved');
530						}
531					});
532				}
533			}
534
535			//  We need to create an object to store our property in
536			//  since we don't know what it'll be.
537			var obj = {};
538
539			//  Manually create it here
540			obj[prop] = -(100 * to) + '%';
541
542			//  And animate using our newly-created object
543			return self._move(self.$container, obj);
544		};
545
546
547		//  Fade between slides rather than, uh, sliding it
548		self.animateFade = function(to) {
549			var $active = self.$slides.eq(to).addClass(self.options.activeClass);
550
551			//  Toggle our classes
552			self._move($active.siblings().removeClass(self.options.activeClass), {opacity: 0});
553			self._move($active, {opacity: 1}, false);
554		};
555
556		self._move = function($el, obj, callback, speed) {
557			if(callback !== false) {
558				callback = function() {
559					self.$context.trigger(self._ + '.moved');
560				};
561			}
562
563			return $el._move(obj, speed || self.options.speed, self.options.easing, callback);
564		};
565
566		//  Allow daisy-chaining of methods
567		return self.init(options);
568	};
569
570	//  Internal (but global) jQuery methods
571	//  They're both just helpful types of shorthand for
572	//  anything that might take too long to write out or
573	//  something that might be used more than once.
574	$.fn._toggleActive = function(className) {
575		return this.addClass(className).siblings().removeClass(className);
576	};
577
578	//  The equivalent to PHP's ucfirst(). Take the first
579	//  character of a string and make it uppercase.
580	//  Simples.
581	$._ucfirst = function(str) {
582		//  Take our variable, run a regex on the first letter
583		return (str + '').toLowerCase().replace(/^./, function(match) {
584			//  And uppercase it. Simples.
585			return match.toUpperCase();
586		});
587	};
588
589	$.fn._move = function() {
590		var type = 'animate';
591
592		this.stop(true, true);
593
594		if($.fn.velocity) {
595			type = 'velocity';
596		}
597
598		return $.fn[type].apply(this, arguments);
599	};
600
601	//  And set up our jQuery plugin
602	$.fn.unslider = function(opts) {
603		return this.each(function() {
604			var $this = $(this);
605
606			//  Allow usage of .unslider('function_name')
607			//  as well as using .data('unslider') to access the
608			//  main Unslider object
609			if(typeof opts === 'string' && $this.data('unslider')) {
610				opts = opts.split(':');
611
612				var fn = opts[0];
613				var call = $this.data('unslider')[fn];
614
615				//  Do we have arguments to pass to the string-function?
616				if(opts[1]) {
617					var args = opts[1].split(',');
618					return $.isFunction(call) && call.apply($this, args);
619				}
620
621				$.isFunction(call) && call();
622
623				return this;
624			}
625
626			return $this.data('unslider', new $.Unslider($this, opts));
627		});
628	};
629	
630})(window.jQuery);

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.