PageSourceSearch

https://dsso.kr/js/sly.js

js dsso.kr collected 2026-09-24 11:49:18 UTC 62,635 bytes, 2,210 lines download raw bytes

1/*!
2 * sly 1.6.1 - 8th Aug 2015
3 * https://github.com/darsain/sly
4 *
5 * Licensed under the MIT license.
6 * http://opensource.org/licenses/MIT
7 */
8
9;(function ($, w, undefined) {
10	'use strict';
11
12	var pluginName = 'sly';
13	var className  = 'Sly';
14	var namespace  = pluginName;
15
16	// Local WindowAnimationTiming interface
17	var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame;
18	var rAF = w.requestAnimationFrame;
19
20	// Support indicators
21	var transform, gpuAcceleration;
22
23	// Other global values
24	var $doc = $(document);
25	var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace;
26	var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace;
27	var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace;
28	var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel.' : 'mousewheel.') + namespace;
29	var clickEvent = 'click.' + namespace;
30	var mouseDownEvent = 'mousedown.' + namespace;
31	var interactiveElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA'];
32	var tmpArray = [];
33	var time;
34
35	// Math shorthands
36	var abs = Math.abs;
37	var sqrt = Math.sqrt;
38	var pow = Math.pow;
39	var round = Math.round;
40	var max = Math.max;
41	var min = Math.min;
42
43	// Keep track of last fired global wheel event
44	var lastGlobalWheel = 0;
45	$doc.on(wheelEvent, function (event) {
46		var sly = event.originalEvent[namespace];
47		var time = +new Date();
48		// Update last global wheel time, but only when event didn't originate
49		// in Sly frame, or the origin was less than scrollHijack time ago
50		if (!sly || sly.options.scrollHijack < time - lastGlobalWheel) lastGlobalWheel = time;
51	});
52
53	/**
54	 * Sly.
55	 *
56	 * @class
57	 *
58	 * @param {Element} frame       DOM element of sly container.
59	 * @param {Object}  options     Object with options.
60	 * @param {Object}  callbackMap Callbacks map.
61	 */
62	function Sly(frame, options, callbackMap) {
63		if (!(this instanceof Sly)) return new Sly(frame, options, callbackMap);
64
65		// Extend options
66		var o = $.extend({}, Sly.defaults, options);
67
68		// Private variables
69		var self = this;
70		var parallax = isNumber(frame);
71
72		// Frame
73		var $frame = $(frame);
74		var $slidee = o.slidee ? $(o.slidee).eq(0) : $frame.children().eq(0);
75		var frameSize = 0;
76		var slideeSize = 0;
77		var pos = {
78			start: 0,
79			center: 0,
80			end: 0,
81			cur: 0,
82			dest: 0
83		};
84
85		// Scrollbar
86		var $sb = $(o.scrollBar).eq(0);
87		var $handle = $sb.children().eq(0);
88		var sbSize = 0;
89		var handleSize = 0;
90		var hPos = {
91			start: 0,
92			end: 0,
93			cur: 0
94		};
95
96		// Pagesbar
97		var $pb = $(o.pagesBar);
98		var $pages = 0;
99		var pages = [];
100
101		// Items
102		var $items = 0;
103		var items = [];
104		var rel = {
105			firstItem: 0,
106			lastItem: 0,
107			centerItem: 0,
108			activeItem: null,
109			activePage: 0
110		};
111
112		// Styles
113		var frameStyles = new StyleRestorer($frame[0]);
114		var slideeStyles = new StyleRestorer($slidee[0]);
115		var sbStyles = new StyleRestorer($sb[0]);
116		var handleStyles = new StyleRestorer($handle[0]);
117
118		// Navigation type booleans
119		var basicNav = o.itemNav === 'basic';
120		var forceCenteredNav = o.itemNav === 'forceCentered';
121		var centeredNav = o.itemNav === 'centered' || forceCenteredNav;
122		var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav);
123
124		// Miscellaneous
125		var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame;
126		var $dragSource = o.dragSource ? $(o.dragSource) : $frame;
127		var $forwardButton = $(o.forward);
128		var $backwardButton = $(o.backward);
129		var $prevButton = $(o.prev);
130		var $nextButton = $(o.next);
131		var $prevPageButton = $(o.prevPage);
132		var $nextPageButton = $(o.nextPage);
133		var callbacks = {};
134		var last = {};
135		var animation = {};
136		var move = {};
137		var dragging = {
138			released: 1
139		};
140		var scrolling = {
141			last: 0,
142			delta: 0,
143			resetTime: 200
144		};
145		var renderID = 0;
146		var historyID = 0;
147		var cycleID = 0;
148		var continuousID = 0;
149		var i, l;
150
151		// Normalizing frame
152		if (!parallax) {
153			frame = $frame[0];
154		}
155
156		// Expose properties
157		self.initialized = 0;
158		self.frame = frame;
159		self.slidee = $slidee[0];
160		self.pos = pos;
161		self.rel = rel;
162		self.items = items;
163		self.pages = pages;
164		self.isPaused = 0;
165		self.options = o;
166		self.dragging = dragging;
167
168		/**
169		 * Loading function.
170		 *
171		 * Populate arrays, set sizes, bind events, ...
172		 *
173		 * @param {Boolean} [isInit] Whether load is called from within self.init().
174		 * @return {Void}
175		 */
176		function load(isInit) {
177			// Local variables
178			var lastItemsCount = 0;
179			var lastPagesCount = pages.length;
180
181			// Save old position
182			pos.old = $.extend({}, pos);
183
184			// Reset global variables
185			frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height']();
186			sbSize = $sb[o.horizontal ? 'width' : 'height']();
187			slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight']();
188			pages.length = 0;
189
190			// Set position limits & relatives
191			pos.start = 0;
192			pos.end = max(slideeSize - frameSize, 0);
193
194			// Sizes & offsets for item based navigations
195			if (itemNav) {
196				// Save the number of current items
197				lastItemsCount = items.length;
198
199				// Reset itemNav related variables
200				$items = $slidee.children(o.itemSelector);
201				items.length = 0;
202
203				// Needed variables
204				var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop');
205				var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom');
206				var borderBox = $($items).css('boxSizing') === 'border-box';
207				var areFloated = $items.css('float') !== 'none';
208				var ignoredMargin = 0;
209				var lastItemIndex = $items.length - 1;
210				var lastItem;
211
212				// Reset slideeSize
213				slideeSize = 0;
214
215				// Iterate through items
216				$items.each(function (i, element) {
217					// Item
218					var $item = $(element);
219					var rect = element.getBoundingClientRect();
220					var itemSize = round(o.horizontal ? rect.width || rect.right - rect.left : rect.height || rect.bottom - rect.top);
221					var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop');
222					var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom');
223					var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd;
224					var singleSpaced = !itemMarginStart || !itemMarginEnd;
225					var item = {};
226					item.el = element;
227					item.size = singleSpaced ? itemSize : itemSizeFull;
228					item.half = item.size / 2;
229					item.start = slideeSize + (singleSpaced ? itemMarginStart : 0);
230					item.center = item.start - round(frameSize / 2 - item.size / 2);
231					item.end = item.start - frameSize + item.size;
232
233					// Account for slidee padding
234					if (!i) {
235						slideeSize += paddingStart;
236					}
237
238					// Increment slidee size for size of the active element
239					slideeSize += itemSizeFull;
240
241					// Try to account for vertical margin collapsing in vertical mode
242					// It's not bulletproof, but should work in 99% of cases
243					if (!o.horizontal && !areFloated) {
244						// Subtract smaller margin, but only when top margin is not 0, and this is not the first element
245						if (itemMarginEnd && itemMarginStart && i > 0) {
246							slideeSize -= min(itemMarginStart, itemMarginEnd);
247						}
248					}
249
250					// Things to be done on last item
251					if (i === lastItemIndex) {
252						item.end += paddingEnd;
253						slideeSize += paddingEnd;
254						ignoredMargin = singleSpaced ? itemMarginEnd : 0;
255					}
256
257					// Add item object to items array
258					items.push(item);
259					lastItem = item;
260				});
261
262				// Resize SLIDEE to fit all items
263				$slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px';
264
265				// Adjust internal SLIDEE size for last margin
266				slideeSize -= ignoredMargin;
267
268				// Set limits
269				if (items.length) {
270					pos.start =  items[0][forceCenteredNav ? 'center' : 'start'];
271					pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start;
272				} else {
273					pos.start = pos.end = 0;
274				}
275			}
276
277			// Calculate SLIDEE center position
278			pos.center = round(pos.end / 2 + pos.start / 2);
279
280			// Update relative positions
281			updateRelatives();
282
283			// Scrollbar
284			if ($handle.length && sbSize > 0) {
285				// Stretch scrollbar handle to represent the visible area
286				if (o.dynamicHandle) {
287					handleSize = pos.start === pos.end ? sbSize : round(sbSize * frameSize / slideeSize);
288					handleSize = within(handleSize, o.minHandleSize, sbSize);
289					$handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px';
290				} else {
291					handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight']();
292				}
293
294				hPos.end = sbSize - handleSize;
295
296				if (!renderID) {
297					syncScrollbar();
298				}
299			}
300
301			// Pages
302			if (!parallax && frameSize > 0) {
303				var tempPagePos = pos.start;
304				var pagesHtml = '';
305
306				// Populate pages array
307				if (itemNav) {
308					$.each(items, function (i, item) {
309						if (forceCenteredNav) {
310							pages.push(item.center);
311						} else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) {
312							tempPagePos = item.start;
313							pages.push(tempPagePos);
314							tempPagePos += frameSize;
315							if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) {
316								pages.push(pos.end);
317							}
318						}
319					});
320				} else {
321					while (tempPagePos - frameSize < pos.end) {
322						pages.push(tempPagePos);
323						tempPagePos += frameSize;
324					}
325				}
326
327				// Pages bar
328				if ($pb[0] && lastPagesCount !== pages.length) {
329					for (var i = 0; i < pages.length; i++) {
330						pagesHtml += o.pageBuilder.call(self, i);
331					}
332					$pages = $pb.html(pagesHtml).children();
333					$pages.eq(rel.activePage).addClass(o.activeClass);
334				}
335			}
336
337			// Extend relative variables object with some useful info
338			rel.slideeSize = slideeSize;
339			rel.frameSize = frameSize;
340			rel.sbSize = sbSize;
341			rel.handleSize = handleSize;
342
343			// Activate requested position
344			if (itemNav) {
345				if (isInit && o.startAt != null) {
346					activate(o.startAt);
347					self[centeredNav ? 'toCenter' : 'toStart'](o.startAt);
348				}
349				// Fix possible overflowing
350				var activeItem = items[rel.activeItem];
351				slideTo(centeredNav && activeItem ? activeItem.center : within(pos.dest, pos.start, pos.end));
352			} else {
353				if (isInit) {
354					if (o.startAt != null) slideTo(o.startAt, 1);
355				} else {
356					// Fix possible overflowing
357					slideTo(within(pos.dest, pos.start, pos.end));
358				}
359			}
360
361			// Trigger load event
362			trigger('load');
363		}
364		self.reload = function () { load(); };
365
366		/**
367		 * Animate to a position.
368		 *
369		 * @param {Int}  newPos    New position.
370		 * @param {Bool} immediate Reposition immediately without an animation.
371		 * @param {Bool} dontAlign Do not align items, use the raw position passed in first argument.
372		 *
373		 * @return {Void}
374		 */
375		function slideTo(newPos, immediate, dontAlign) {
376			// Align items
377			if (itemNav && dragging.released && !dontAlign) {
378				var tempRel = getRelatives(newPos);
379				var isNotBordering = newPos > pos.start && newPos < pos.end;
380
381				if (centeredNav) {
382					if (isNotBordering) {
383						newPos = items[tempRel.centerItem].center;
384					}
385					if (forceCenteredNav && o.activateMiddle) {
386						activate(tempRel.centerItem);
387					}
388				} else if (isNotBordering) {
389					newPos = items[tempRel.firstItem].start;
390				}
391			}
392
393			// Handle overflowing position limits
394			if (dragging.init && dragging.slidee && o.elasticBounds) {
395				if (newPos > pos.end) {
396					newPos = pos.end + (newPos - pos.end) / 6;
397				} else if (newPos < pos.start) {
398					newPos = pos.start + (newPos - pos.start) / 6;
399				}
400			} else {
401				newPos = within(newPos, pos.start, pos.end);
402			}
403
404			// Update the animation object
405			animation.start = +new Date();
406			animation.time = 0;
407			animation.from = pos.cur;
408			animation.to = newPos;
409			animation.delta = newPos - pos.cur;
410			animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee;
411			animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed);
412
413			// Reset dragging tweesing request
414			dragging.tweese = 0;
415
416			// Start animation rendering
417			if (newPos !== pos.dest) {
418				pos.dest = newPos;
419				trigger('change');
420				if (!renderID) {
421					render();
422				}
423			}
424
425			// Reset next cycle timeout
426			resetCycle();
427
428			// Synchronize states
429			updateRelatives();
430			updateButtonsState();
431			syncPagesbar();
432		}
433
434		/**
435		 * Render animation frame.
436		 *
437		 * @return {Void}
438		 */
439		function render() {
440			if (!self.initialized) {
441				return;
442			}
443
444			// If first render call, wait for next animationFrame
445			if (!renderID) {
446				renderID = rAF(render);
447				if (dragging.released) {
448					trigger('moveStart');
449				}
450				return;
451			}
452
453			// If immediate repositioning is requested, don't animate.
454			if (animation.immediate) {
455				pos.cur = animation.to;
456			}
457			// Use tweesing for animations without known end point
458			else if (animation.tweesing) {
459				animation.tweeseDelta = animation.to - pos.cur;
460				// Fuck Zeno's paradox
461				if (abs(animation.tweeseDelta) < 0.1) {
462					pos.cur = animation.to;
463				} else {
464					pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed);
465				}
466			}
467			// Use tweening for basic animations with known end point
468			else {
469				animation.time = min(+new Date() - animation.start, o.speed);
470				pos.cur = animation.from + animation.delta * $.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed);
471			}
472
473			// If there is nothing more to render break the rendering loop, otherwise request new animation frame.
474			if (animation.to === pos.cur) {
475				pos.cur = animation.to;
476				dragging.tweese = renderID = 0;
477			} else {
478				renderID = rAF(render);
479			}
480
481			trigger('move');
482
483			// Update SLIDEE position
484			if (!parallax) {
485				if (transform) {
486					$slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)';
487				} else {
488					$slidee[0].style[o.horizontal ? 'left' : 'top'] = -round(pos.cur) + 'px';
489				}
490			}
491
492			// When animation reached the end, and dragging is not active, trigger moveEnd
493			if (!renderID && dragging.released) {
494				trigger('moveEnd');
495			}
496
497			syncScrollbar();
498		}
499
500		/**
501		 * Synchronizes scrollbar with the SLIDEE.
502		 *
503		 * @return {Void}
504		 */
505		function syncScrollbar() {
506			if ($handle.length) {
507				hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end;
508				hPos.cur = within(round(hPos.cur), hPos.start, hPos.end);
509				if (last.hPos !== hPos.cur) {
510					last.hPos = hPos.cur;
511					if (transform) {
512						$handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)';
513					} else {
514						$handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px';
515					}
516				}
517			}
518		}
519
520		/**
521		 * Synchronizes pagesbar with SLIDEE.
522		 *
523		 * @return {Void}
524		 */
525		function syncPagesbar() {
526			if ($pages[0] && last.page !== rel.activePage) {
527				last.page = rel.activePage;
528				$pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass);
529				trigger('activePage', last.page);
530			}
531		}
532
533		/**
534		 * Returns the position object.
535		 *
536		 * @param {Mixed} item
537		 *
538		 * @return {Object}
539		 */
540		self.getPos = function (item) {
541			if (itemNav) {
542				var index = getIndex(item);
543				return index !== -1 ? items[index] : false;
544			} else {
545				var $item = $slidee.find(item).eq(0);
546
547				if ($item[0]) {
548					var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top;
549					var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight']();
550
551					return {
552						start: offset,
553						center: offset - frameSize / 2 + size / 2,
554						end: offset - frameSize + size,
555						size: size
556					};
557				} else {
558					return false;
559				}
560			}
561		};
562
563		/**
564		 * Continuous move in a specified direction.
565		 *
566		 * @param  {Bool} forward True for forward movement, otherwise it'll go backwards.
567		 * @param  {Int}  speed   Movement speed in pixels per frame. Overrides options.moveBy value.
568		 *
569		 * @return {Void}
570		 */
571		self.moveBy = function (speed) {
572			move.speed = speed;
573			// If already initiated, or there is nowhere to move, abort
574			if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
575				return;
576			}
577			// Initiate move object
578			move.lastTime = +new Date();
579			move.startPos = pos.cur;
580			// Set dragging as initiated
581			continuousInit('button');
582			dragging.init = 1;
583			// Start movement
584			trigger('moveStart');
585			cAF(continuousID);
586			moveLoop();
587		};
588
589		/**
590		 * Continuous movement loop.
591		 *
592		 * @return {Void}
593		 */
594		function moveLoop() {
595			// If there is nowhere to move anymore, stop
596			if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
597				self.stop();
598			}
599			// Request new move loop if it hasn't been stopped
600			continuousID = dragging.init ? rAF(moveLoop) : 0;
601			// Update move object
602			move.now = +new Date();
603			move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed;
604			// Slide
605			slideTo(dragging.init ? move.pos : round(move.pos));
606			// Normally, this is triggered in render(), but if there
607			// is nothing to render, we have to do it manually here.
608			if (!dragging.init && pos.cur === pos.dest) {
609				trigger('moveEnd');
610			}
611			// Update times for future iteration
612			move.lastTime = move.now;
613		}
614
615		/**
616		 * Stops continuous movement.
617		 *
618		 * @return {Void}
619		 */
620		self.stop = function () {
621			if (dragging.source === 'button') {
622				dragging.init = 0;
623				dragging.released = 1;
624			}
625		};
626
627		/**
628		 * Activate previous item.
629		 *
630		 * @return {Void}
631		 */
632		self.prev = function () {
633			self.activate(rel.activeItem == null ? 0 : rel.activeItem - 1);
634		};
635
636		/**
637		 * Activate next item.
638		 *
639		 * @return {Void}
640		 */
641		self.next = function () {
642			self.activate(rel.activeItem == null ? 0 : rel.activeItem + 1);
643		};
644
645		/**
646		 * Activate previous page.
647		 *
648		 * @return {Void}
649		 */
650		self.prevPage = function () {
651			self.activatePage(rel.activePage - 1);
652		};
653
654		/**
655		 * Activate next page.
656		 *
657		 * @return {Void}
658		 */
659		self.nextPage = function () {
660			self.activatePage(rel.activePage + 1);
661		};
662
663		/**
664		 * Slide SLIDEE by amount of pixels.
665		 *
666		 * @param {Int}  delta     Pixels/Items. Positive means forward, negative means backward.
667		 * @param {Bool} immediate Reposition immediately without an animation.
668		 *
669		 * @return {Void}
670		 */
671		self.slideBy = function (delta, immediate) {
672			if (!delta) {
673				return;
674			}
675			if (itemNav) {
676				self[centeredNav ? 'toCenter' : 'toStart'](
677					within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length)
678				);
679			} else {
680				slideTo(pos.dest + delta, immediate);
681			}
682		};
683
684		/**
685		 * Animate SLIDEE to a specific position.
686		 *
687		 * @param {Int}  pos       New position.
688		 * @param {Bool} immediate Reposition immediately without an animation.
689		 *
690		 * @return {Void}
691		 */
692		self.slideTo = function (pos, immediate) {
693			slideTo(pos, immediate);
694		};
695
696		/**
697		 * Core method for handling `toLocation` methods.
698		 *
699		 * @param  {String} location
700		 * @param  {Mixed}  item
701		 * @param  {Bool}   immediate
702		 *
703		 * @return {Void}
704		 */
705		function to(location, item, immediate) {
706			// Optional arguments logic
707			if (type(item) === 'boolean') {
708				immediate = item;
709				item = undefined;
710			}
711
712			if (item === undefined) {
713				slideTo(pos[location], immediate);
714			} else {
715				// You can't align items to sides of the frame
716				// when centered navigation type is enabled
717				if (centeredNav && location !== 'center') {
718					return;
719				}
720
721				var itemPos = self.getPos(item);
722				if (itemPos) {
723					slideTo(itemPos[location], immediate, !centeredNav);
724				}
725			}
726		}
727
728		/**
729		 * Animate element or the whole SLIDEE to the start of the frame.
730		 *
731		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
732		 * @param {Bool}  immediate Reposition immediately without an animation.
733		 *
734		 * @return {Void}
735		 */
736		self.toStart = function (item, immediate) {
737			to('start', item, immediate);
738		};
739
740		/**
741		 * Animate element or the whole SLIDEE to the end of the frame.
742		 *
743		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
744		 * @param {Bool}  immediate Reposition immediately without an animation.
745		 *
746		 * @return {Void}
747		 */
748		self.toEnd = function (item, immediate) {
749			to('end', item, immediate);
750		};
751
752		/**
753		 * Animate element or the whole SLIDEE to the center of the frame.
754		 *
755		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
756		 * @param {Bool}  immediate Reposition immediately without an animation.
757		 *
758		 * @return {Void}
759		 */
760		self.toCenter = function (item, immediate) {
761			to('center', item, immediate);
762		};
763
764		/**
765		 * Get the index of an item in SLIDEE.
766		 *
767		 * @param {Mixed} item     Item DOM element.
768		 *
769		 * @return {Int}  Item index, or -1 if not found.
770		 */
771		function getIndex(item) {
772			return item != null ?
773					isNumber(item) ?
774						item >= 0 && item < items.length ? item : -1 :
775						$items.index(item) :
776					-1;
777		}
778		// Expose getIndex without lowering the compressibility of it,
779		// as it is used quite often throughout Sly.
780		self.getIndex = getIndex;
781
782		/**
783		 * Get index of an item in SLIDEE based on a variety of input types.
784		 *
785		 * @param  {Mixed} item DOM element, positive or negative integer.
786		 *
787		 * @return {Int}   Item index, or -1 if not found.
788		 */
789		function getRelativeIndex(item) {
790			return getIndex(isNumber(item) && item < 0 ? item + items.length : item);
791		}
792
793		/**
794		 * Activates an item.
795		 *
796		 * @param  {Mixed} item Item DOM element, or index starting at 0.
797		 *
798		 * @return {Mixed} Activated item index or false on fail.
799		 */
800		function activate(item, force) {
801			var index = getIndex(item);
802
803			if (!itemNav || index < 0) {
804				return false;
805			}
806
807			// Update classes, last active index, and trigger active event only when there
808			// has been a change. Otherwise just return the current active index.
809			if (last.active !== index || force) {
810				// Update classes
811				$items.eq(rel.activeItem).removeClass(o.activeClass);
812				$items.eq(index).addClass(o.activeClass);
813
814				last.active = rel.activeItem = index;
815
816				updateButtonsState();
817				trigger('active', index);
818			}
819
820			return index;
821		}
822
823		/**
824		 * Activates an item and helps with further navigation when o.smart is enabled.
825		 *
826		 * @param {Mixed} item      Item DOM element, or index starting at 0.
827		 * @param {Bool}  immediate Whether to reposition immediately in smart navigation.
828		 *
829		 * @return {Void}
830		 */
831		self.activate = function (item, immediate) {
832			var index = activate(item);
833
834			// Smart navigation
835			if (o.smart && index !== false) {
836				// When centeredNav is enabled, center the element.
837				// Otherwise, determine where to position the element based on its current position.
838				// If the element is currently on the far end side of the frame, assume that user is
839				// moving forward and animate it to the start of the visible frame, and vice versa.
840				if (centeredNav) {
841					self.toCenter(index, immediate);
842				} else if (index >= rel.lastItem) {
843					self.toStart(index, immediate);
844				} else if (index <= rel.firstItem) {
845					self.toEnd(index, immediate);
846				} else {
847					resetCycle();
848				}
849			}
850		};
851
852		/**
853		 * Activates a page.
854		 *
855		 * @param {Int}  index     Page index, starting from 0.
856		 * @param {Bool} immediate Whether to reposition immediately without animation.
857		 *
858		 * @return {Void}
859		 */
860		self.activatePage = function (index, immediate) {
861			if (isNumber(index)) {
862				slideTo(pages[within(index, 0, pages.length - 1)], immediate);
863			}
864		};
865
866		/**
867		 * Return relative positions of items based on their visibility within FRAME.
868		 *
869		 * @param {Int} slideePos Position of SLIDEE.
870		 *
871		 * @return {Void}
872		 */
873		function getRelatives(slideePos) {
874			slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end);
875
876			var relatives = {};
877			var centerOffset = forceCenteredNav ? 0 : frameSize / 2;
878
879			// Determine active page
880			if (!parallax) {
881				for (var p = 0, pl = pages.length; p < pl; p++) {
882					if (slideePos >= pos.end || p === pages.length - 1) {
883						relatives.activePage = pages.length - 1;
884						break;
885					}
886
887					if (slideePos <= pages[p] + centerOffset) {
888						relatives.activePage = p;
889						break;
890					}
891				}
892			}
893
894			// Relative item indexes
895			if (itemNav) {
896				var first = false;
897				var last = false;
898				var center = false;
899
900				// From start
901				for (var i = 0, il = items.length; i < il; i++) {
902					// First item
903					if (first === false && slideePos <= items[i].start + items[i].half) {
904						first = i;
905					}
906
907					// Center item
908					if (center === false && slideePos <= items[i].center + items[i].half) {
909						center = i;
910					}
911
912					// Last item
913					if (i === il - 1 || slideePos <= items[i].end + items[i].half) {
914						last = i;
915						break;
916					}
917				}
918
919				// Safe assignment, just to be sure the false won't be returned
920				relatives.firstItem = isNumber(first) ? first : 0;
921				relatives.centerItem = isNumber(center) ? center : relatives.firstItem;
922				relatives.lastItem = isNumber(last) ? last : relatives.centerItem;
923			}
924
925			return relatives;
926		}
927
928		/**
929		 * Update object with relative positions.
930		 *
931		 * @param {Int} newPos
932		 *
933		 * @return {Void}
934		 */
935		function updateRelatives(newPos) {
936			$.extend(rel, getRelatives(newPos));
937		}
938
939		/**
940		 * Disable navigation buttons when needed.
941		 *
942		 * Adds disabledClass, and when the button is <button> or <input>, activates :disabled state.
943		 *
944		 * @return {Void}
945		 */
946		function updateButtonsState() {
947			var isStart = pos.dest <= pos.start;
948			var isEnd = pos.dest >= pos.end;
949			var slideePosState = (isStart ? 1 : 0) | (isEnd ? 2 : 0);
950
951			// Update paging buttons only if there has been a change in SLIDEE position
952			if (last.slideePosState !== slideePosState) {
953				last.slideePosState = slideePosState;
954
955				if ($prevPageButton.is('button,input')) {
956					$prevPageButton.prop('disabled', isStart);
957				}
958
959				if ($nextPageButton.is('button,input')) {
960					$nextPageButton.prop('disabled', isEnd);
961				}
962
963				$prevPageButton.add($backwardButton)[isStart ? 'addClass' : 'removeClass'](o.disabledClass);
964				$nextPageButton.add($forwardButton)[isEnd ? 'addClass' : 'removeClass'](o.disabledClass);
965			}
966
967			// Forward & Backward buttons need a separate state caching because we cannot "property disable"
968			// them while they are being used, as disabled buttons stop emitting mouse events.
969			if (last.fwdbwdState !== slideePosState && dragging.released) {
970				last.fwdbwdState = slideePosState;
971
972				if ($backwardButton.is('button,input')) {
973					$backwardButton.prop('disabled', isStart);
974				}
975
976				if ($forwardButton.is('button,input')) {
977					$forwardButton.prop('disabled', isEnd);
978				}
979			}
980
981			// Item navigation
982			if (itemNav && rel.activeItem != null) {
983				var isFirst = rel.activeItem === 0;
984				var isLast = rel.activeItem >= items.length - 1;
985				var itemsButtonState = (isFirst ? 1 : 0) | (isLast ? 2 : 0);
986
987				if (last.itemsButtonState !== itemsButtonState) {
988					last.itemsButtonState = itemsButtonState;
989
990					if ($prevButton.is('button,input')) {
991						$prevButton.prop('disabled', isFirst);
992					}
993
994					if ($nextButton.is('button,input')) {
995						$nextButton.prop('disabled', isLast);
996					}
997
998					$prevButton[isFirst ? 'addClass' : 'removeClass'](o.disabledClass);
999					$nextButton[isLast ? 'addClass' : 'removeClass'](o.disabledClass);
1000				}
1001			}
1002		}
1003
1004		/**
1005		 * Resume cycling.
1006		 *
1007		 * @param {Int} priority Resume pause with priority lower or equal than this. Used internally for pauseOnHover.
1008		 *
1009		 * @return {Void}
1010		 */
1011		self.resume = function (priority) {
1012			if (!o.cycleBy || !o.cycleInterval || o.cycleBy === 'items' && (!items[0] || rel.activeItem == null) || priority < self.isPaused) {
1013				return;
1014			}
1015
1016			self.isPaused = 0;
1017
1018			if (cycleID) {
1019				cycleID = clearTimeout(cycleID);
1020			} else {
1021				trigger('resume');
1022			}
1023
1024			cycleID = setTimeout(function () {
1025				trigger('cycle');
1026				switch (o.cycleBy) {
1027					case 'items':
1028						self.activate(rel.activeItem >= items.length - 1 ? 0 : rel.activeItem + 1);
1029						break;
1030
1031					case 'pages':
1032						self.activatePage(rel.activePage >
1032= pages.length - 1 ? 0 : rel.activePage + 1);
1033						break;
1034				}
1035			}, o.cycleInterval);
1036		};
1037
1038		/**
1039		 * Pause cycling.
1040		 *
1041		 * @param {Int} priority Pause priority. 100 is default. Used internally for pauseOnHover.
1042		 *
1043		 * @return {Void}
1044		 */
1045		self.pause = function (priority) {
1046			if (priority < self.isPaused) {
1047				return;
1048			}
1049
1050			self.isPaused = priority || 100;
1051
1052			if (cycleID) {
1053				cycleID = clearTimeout(cycleID);
1054				trigger('pause');
1055			}
1056		};
1057
1058		/**
1059		 * Toggle cycling.
1060		 *
1061		 * @return {Void}
1062		 */
1063		self.toggle = function () {
1064			self[cycleID ? 'pause' : 'resume']();
1065		};
1066
1067		/**
1068		 * Updates a signle or multiple option values.
1069		 *
1070		 * @param {Mixed} name  Name of the option that should be updated, or object that will extend the options.
1071		 * @param {Mixed} value New option value.
1072		 *
1073		 * @return {Void}
1074		 */
1075		self.set = function (name, value) {
1076			if ($.isPlainObject(name)) {
1077				$.extend(o, name);
1078			} else if (o.hasOwnProperty(name)) {
1079				o[name] = value;
1080			}
1081		};
1082
1083		/**
1084		 * Add one or multiple items to the SLIDEE end, or a specified position index.
1085		 *
1086		 * @param {Mixed} element Node element, or HTML string.
1087		 * @param {Int}   index   Index of a new item position. By default item is appended at the end.
1088		 *
1089		 * @return {Void}
1090		 */
1091		self.add = function (element, index) {
1092			var $element = $(element);
1093
1094			if (itemNav) {
1095				// Insert the element(s)
1096				if (index == null || !items[0] || index >= items.length) {
1097					$element.appendTo($slidee);
1098				} else if (items.length) {
1099					$element.insertBefore(items[index].el);
1100				}
1101
1102				// Adjust the activeItem index
1103				if (rel.activeItem != null && index <= rel.activeItem) {
1104					last.active = rel.activeItem += $element.length;
1105				}
1106			} else {
1107				$slidee.append($element);
1108			}
1109
1110			// Reload
1111			load();
1112		};
1113
1114		/**
1115		 * Remove an item from SLIDEE.
1116		 *
1117		 * @param {Mixed} element Item index, or DOM element.
1118		 * @param {Int}   index   Index of a new item position. By default item is appended at the end.
1119		 *
1120		 * @return {Void}
1121		 */
1122		self.remove = function (element) {
1123			if (itemNav) {
1124				var index = getRelativeIndex(element);
1125
1126				if (index > -1) {
1127					// Remove the element
1128					$items.eq(index).remove();
1129
1130					// If the current item is being removed, activate new one after reload
1131					var reactivate = index === rel.activeItem;
1132
1133					// Adjust the activeItem index
1134					if (rel.activeItem != null && index < rel.activeItem) {
1135						last.active = --rel.activeItem;
1136					}
1137
1138					// Reload
1139					load();
1140
1141					// Activate new item at the removed position
1142					if (reactivate) {
1143						last.active = null;
1144						self.activate(rel.activeItem);
1145					}
1146				}
1147			} else {
1148				$(element).remove();
1149				load();
1150			}
1151		};
1152
1153		/**
1154		 * Helps re-arranging items.
1155		 *
1156		 * @param  {Mixed} item     Item DOM element, or index starting at 0. Use negative numbers to select items from the end.
1157		 * @param  {Mixed} position Item insertion anchor. Accepts same input types as item argument.
1158		 * @param  {Bool}  after    Insert after instead of before the anchor.
1159		 *
1160		 * @return {Void}
1161		 */
1162		function moveItem(item, position, after) {
1163			item = getRelativeIndex(item);
1164			position = getRelativeIndex(position);
1165
1166			// Move only if there is an actual change requested
1167			if (item > -1 && position > -1 && item !== position && (!after || position !== item - 1) && (after || position !== item + 1)) {
1168				$items.eq(item)[after ? 'insertAfter' : 'insertBefore'](items[position].el);
1169
1170				var shiftStart = item < position ? item : (after ? position : position - 1);
1171				var shiftEnd = item > position ? item : (after ? position + 1 : position);
1172				var shiftsUp = item > position;
1173
1174				// Update activeItem index
1175				if (rel.activeItem != null) {
1176					if (item === rel.activeItem) {
1177						last.active = rel.activeItem = after ? (shiftsUp ? position + 1 : position) : (shiftsUp ? position : position - 1);
1178					} else if (rel.activeItem > shiftStart && rel.activeItem < shiftEnd) {
1179						last.active = rel.activeItem += shiftsUp ? 1 : -1;
1180					}
1181				}
1182
1183				// Reload
1184				load();
1185			}
1186		}
1187
1188		/**
1189		 * Move item after the target anchor.
1190		 *
1191		 * @param  {Mixed} item     Item to be moved. Can be DOM element or item index.
1192		 * @param  {Mixed} position Target position anchor. Can be DOM element or item index.
1193		 *
1194		 * @return {Void}
1195		 */
1196		self.moveAfter = function (item, position) {
1197			moveItem(item, position, 1);
1198		};
1199
1200		/**
1201		 * Move item before the target anchor.
1202		 *
1203		 * @param  {Mixed} item     Item to be moved. Can be DOM element or item index.
1204		 * @param  {Mixed} position Target position anchor. Can be DOM element or item index.
1205		 *
1206		 * @return {Void}
1207		 */
1208		self.moveBefore = function (item, position) {
1209			moveItem(item, position);
1210		};
1211
1212		/**
1213		 * Registers callbacks.
1214		 *
1215		 * @param  {Mixed}
1215 name  Event name, or callbacks map.
1216		 * @param  {Mixed} fn    Callback, or an array of callback functions.
1217		 *
1218		 * @return {Void}
1219		 */
1220		self.on = function (name, fn) {
1221			// Callbacks map
1222			if (type(name) === 'object') {
1223				for (var key in name) {
1224					if (name.hasOwnProperty(key)) {
1225						self.on(key, name[key]);
1226					}
1227				}
1228			// Callback
1229			} else if (type(fn) === 'function') {
1230				var names = name.split(' ');
1231				for (var n = 0, nl = names.length; n < nl; n++) {
1232					callbacks[names[n]] = callbacks[names[n]] || [];
1233					if (callbackIndex(names[n], fn) === -1) {
1234						callbacks[names[n]].push(fn);
1235					}
1236				}
1237			// Callbacks array
1238			} else if (type(fn) === 'array') {
1239				for (var f = 0, fl = fn.length; f < fl; f++) {
1240					self.on(name, fn[f]);
1241				}
1242			}
1243		};
1244
1245		/**
1246		 * Registers callbacks to be executed only once.
1247		 *
1248		 * @param  {Mixed} name  Event name, or callbacks map.
1249		 * @param  {Mixed} fn    Callback, or an array of callback functions.
1250		 *
1251		 * @return {Void}
1252		 */
1253		self.one = function (name, fn) {
1254			function proxy() {
1255				fn.apply(self, arguments);
1256				self.off(name, proxy);
1257			}
1258			self.on(name, proxy);
1259		};
1260
1261		/**
1262		 * Remove one or all callbacks.
1263		 *
1264		 * @param  {String} name Event name.
1265		 * @param  {Mixed}  fn   Callback, or an array of callback functions. Omit to remove all callbacks.
1266		 *
1267		 * @return {Void}
1268		 */
1269		self.off = function (name, fn) {
1270			if (fn instanceof Array) {
1271				for (var f = 0, fl = fn.length; f < fl; f++) {
1272					self.off(name, fn[f]);
1273				}
1274			} else {
1275				var names = name.split(' ');
1276				for (var n = 0, nl = names.length; n < nl; n++) {
1277					callbacks[names[n]] = callbacks[names[n]] || [];
1278					if (fn == null) {
1279						callbacks[names[n]].length = 0;
1280					} else {
1281						var index = callbackIndex(names[n], fn);
1282						if (index !== -1) {
1283							callbacks[names[n]].splice(index, 1);
1284						}
1285					}
1286				}
1287			}
1288		};
1289
1290		/**
1291		 * Returns callback array index.
1292		 *
1293		 * @param  {String}   name Event name.
1294		 * @param  {Function} fn   Function
1295		 *
1296		 * @return {Int} Callback array index, or -1 if isn't registered.
1297		 */
1298		function callbackIndex(name, fn) {
1299			for (var i = 0, l = callbacks[name].length; i < l; i++) {
1300				if (callbacks[name][i] === fn) {
1301					return i;
1302				}
1303			}
1304			return -1;
1305		}
1306
1307		/**
1308		 * Reset next cycle timeout.
1309		 *
1310		 * @return {Void}
1311		 */
1312		function resetCycle() {
1313			if (dragging.released && !self.isPaused) {
1314				self.resume();
1315			}
1316		}
1317
1318		/**
1319		 * Calculate SLIDEE representation of handle position.
1320		 *
1321		 * @param  {Int} handlePos
1322		 *
1323		 * @return {Int}
1324		 */
1325		function handleToSlidee(handlePos) {
1326			return round(within(handlePos, hPos.start, hPos.end) / hPos.end * (pos.end - pos.start)) + pos.start;
1327		}
1328
1329		/**
1330		 * Keeps track of a dragging delta history.
1331		 *
1332		 * @return {Void}
1333		 */
1334		function draggingHistoryTick() {
1335			// Looking at this, I know what you're thinking :) But as we need only 4 history states, doing it this way
1336			// as opposed to a proper loop is ~25 bytes smaller (when minified with GCC), a lot faster, and doesn't
1337			// generate garbage. The loop version would create 2 new variables on every tick. Unexaptable!
1338			dragging.history[0] = dragging.history[1];
1339			dragging.history[1] = dragging.history[2];
1340			dragging.history[2] = dragging.history[3];
1341			dragging.history[3] = dragging.delta;
1342		}
1343
1344		/**
1345		 * Initialize continuous movement.
1346		 *
1347		 * @return {Void}
1348		 */
1349		function continuousInit(source) {
1350			dragging.released = 0;
1351			dragging.source = source;
1352			dragging.slidee = source === 'slidee';
1353		}
1354
1355		/**
1356		 * Dragging initiator.
1357		 *
1358		 * @param  {Event} event
1359		 *
1360		 * @return {Void}
1361		 */
1362		function dragInit(event) {
1363			var isTouch = event.type === 'touchstart';
1364			var source = event.data.source;
1365			var isSlidee = source === 'slidee';
1366
1367			// Ignore when already in progress, or interactive element in non-touch navivagion
1368			if (dragging.init || !isTouch && isInteractive(event.target)) {
1369				return;
1370			}
1371
1372			// Handle dragging conditions
1373			if (source === 'handle' && (!o.dragHandle || hPos.start === hPos.end)) {
1374				return;
1375			}
1376
1377			// SLIDEE dragging conditions
1378			if (isSlidee && !(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) {
1379				return;
1380			}
1381
1382			if (!isTouch) {
1383				// prevents native image dragging in Firefox
1384				stopDefault(event);
1385			}
1386
1387			// Reset dragging object
1388			continuousInit(source);
1389
1390			// Properties used in dragHandler
1391			dragging.init = 0;
1392			dragging.$source = $(event.target);
1393			dragging.touch = isTouch;
1394			dragging.pointer = isTouch ? event.originalEvent.touches[0] : event;
1395			dragging.initX = dragging.pointer.pageX;
1396			dragging.initY = dragging.pointer.pageY;
1397			dragging.initPos = isSlidee ? pos.cur : hPos.cur;
1398			dragging.start = +new Date();
1399			dragging.time = 0;
1400			dragging.path = 0;
1401			dragging.delta = 0;
1402			dragging.locked = 0;
1403			dragging.history = [0, 0, 0, 0];
1404			dragging.pathToLock = isSlidee ? isTouch ? 30 : 10 : 0;
1405
1406			// Bind dragging events
1407			$doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler);
1408
1409			// Pause ongoing cycle
1410			self.pause(1);
1411
1412			// Add dragging class
1413			(isSlidee ? $slidee : $handle).addClass(o.draggedClass);
1414
1415			// Trigger moveStart event
1416			trigger('moveStart');
1417
1418			// Keep track of a dragging path history. This is later used in the
1419			// dragging release swing calculation when dragging SLIDEE.
1420			if (isSlidee) {
1421				historyID = setInterval(draggingHistoryTick, 10);
1422			}
1423		}
1424
1425		/**
1426		 * Handler for dragging scrollbar handle or SLIDEE.
1427		 *
1428		 * @param  {Event} event
1429		 *
1430		 * @return {Void}
1431		 */
1432		function dragHandler(event) {
1433			dragging.released = event.type === 'mouseup' || event.type === 'touchend';
1434			dragging.pointer = dragging.touch ? event.originalEvent[dragging.released ? 'changedTouches' : 'touches'][0] : event;
1435			dragging.pathX = dragging.pointer.pageX - dragging.initX;
1436			dragging.pathY = dragging.pointer.pageY - dragging.initY;
1437			dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2));
1438			dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY;
1439
1440			if (!dragging.released && dragging.path < 1) return;
1441
1442			// We haven't decided whether this is a drag or not...
1443			if (!dragging.init) {
1444				// If the drag path was very short, maybe it's not a drag?
1445				if (dragging.path < o.dragThreshold) {
1446					// If the pointer was released, the path will not become longer and it's
1447					// definitely not a drag. If not released yet, decide on next iteration
1448					return dragging.released ? dragEnd() : undefined;
1449				}
1450				else {
1451					// If dragging path is sufficiently long we can confidently start a drag
1452					// if drag is in different direction than scroll, ignore it
1453					if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) {
1454						dragging.init = 1;
1455					} else {
1456						return dragEnd();
1457					}
1458				}
1459			}
1460
1461			stopDefault(event);
1462
1463			// Disable click on a source element, as it is unwelcome when dragging
1464			if (!dragging.locked && dragging.path > dragging.pathToLock && dragging.slidee) {
1465				dragging.locked = 1;
1466				dragging.$source.on(clickEvent, disableOneEvent);
1467			}
1468
1469			// Cancel dragging on release
1470			if (dragging.released) {
1471				dragEnd();
1472
1473				// Adjust path with a swing on mouse release
1474				if (o.releaseSwing && dragging.slidee) {
1475					dragging.swing = (dragging.delta - dragging.history[0]) / 40 * 300;
1476					dragging.delta += dragging.swing;
1477					dragging.tweese = abs(dragging.swing) > 10;
1478				}
1479			}
1480
1481			slideTo(dragging.slidee ? round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta));
1482		}
1483
1484		/**
1485		 * Stops dragging and cleans up after it.
1486		 *
1487		 * @return {Void}
1488		 */
1489		function dragEnd() {
1490			clearInterval(historyID);
1491			dragging.released = true;
1492			$doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler);
1493			(dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass);
1494
1495			// Make sure that disableOneEvent is not active in next tick.
1496			setTimeout(function () {
1497				dragging.$source.off(clickEvent, disableOneEvent);
1498			});
1499
1500			// Normally, this is triggered in render(), but if there
1501			// is nothing to render, we have to do it manually here.
1502			if (pos.cur === pos.dest && dragging.init) {
1503				trigger('moveEnd');
1504			}
1505
1506			// Resume ongoing cycle
1507			self.resume(1);
1508
1509			dragging.init = 0;
1510		}
1511
1512		/**
1513		 * Check whether element is interactive.
1514		 *
1515		 * @return {Boolean}
1516		 */
1517		function isInteractive(element) {
1518			return ~$.inArray(element.nodeName, interactiveElements) || $(element).is(o.interactive);
1519		}
1520
1521		/**
1522		 * Continuous movement cleanup on mouseup.
1523		 *
1524		 * @return {Void}
1525		 */
1526		function movementReleaseHandler() {
1527			self.stop();
1528			$doc.off('mouseup', movementReleaseHandler);
1529		}
1530
1531		/**
1532		 * Buttons navigation handler.
1533		 *
1534		 * @param  {Event} event
1535		 *
1536		 * @return {Void}
1537		 */
1538		function buttonsHandler(event) {
1539			/*jshint validthis:true */
1540			stopDefault(event);
1541			switch (this) {
1542				case $forwardButton[0]:
1543				case $backwardButton[0]:
1544					self.moveBy($forwardButton.is(this) ? o.moveBy : -o.moveBy);
1545					$doc.on('mouseup', movementReleaseHandler);
1546					break;
1547
1548				case $prevButton[0]:
1549					self.prev();
1550					break;
1551
1552				case $nextButton[0]:
1553					self.next();
1554					break;
1555
1556				case $prevPageButton[0]:
1557					self.prevPage();
1558					break;
1559
1560				case $nextPageButton[0]:
1561					self.nextPage();
1562					break;
1563			}
1564		}
1565
1566		/**
1567		 * Mouse wheel delta normalization.
1568		 *
1569		 * @param  {Event} event
1570		 *
1571		 * @return {Int}
1572		 */
1573		function normalizeWheelDelta(event) {
1574			// wheelDelta needed only for IE8-
1575			scrolling.curDelta = ((o.horizontal ? event.deltaY || event.deltaX : event.deltaY) || -event.wheelDelta);
1576			scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100;
1577			if (!itemNav) {
1578				return scrolling.curDelta;
1579			}
1580			time = +new Date();
1581			if (scrolling.last < time - scrolling.resetTime) {
1582				scrolling.delta = 0;
1583			}
1584			scrolling.last = time;
1585			scrolling.delta += scrolling.curDelta;
1586			if (abs(scrolling.delta) < 1) {
1587				scrolling.finalDelta = 0;
1588			} else {
1589				scrolling.finalDelta = round(scrolling.delta / 1);
1590				scrolling.delta %= 1;
1591			}
1592			return scrolling.finalDelta;
1593		}
1594
1595		/**
1596		 * Mouse scrolling handler.
1597		 *
1598		 * @param  {Event} event
1599		 *
1600		 * @return {Void}
1601		 */
1602		function scrollHandler(event) {
1603			// Mark event as originating in a Sly instance
1604			event.originalEvent[namespace] = self;
1605			// Don't hijack global scrolling
1606			var time = +new Date();
1607			if (lastGlobalWheel + o.scrollHijack > time && $scrollSource[0] !== document && $scrollSource[0] !== window) {
1608				lastGlobalWheel = time;
1609				return;
1610			}
1611			// Ignore if there is no scrolling to be done
1612			if (!o.scrollBy || pos.start === pos.end) {
1613				return;
1614			}
1615			var delta = normalizeWheelDelta(event.originalEvent);
1616			// Trap scrolling only when necessary and/or requested
1617			if (o.scrollTrap || delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) {
1618				stopDefault(event, 1);
1619			}
1620			self.slideBy(o.scrollBy * delta);
1621		}
1622
1623		/**
1624		 * Scrollbar click handler.
1625		 *
1626		 * @param  {Event} event
1627		 *
1628		 * @return {Void}
1629		 */
1630		function scrollbarHandler(event) {
1631			// Only clicks on scroll bar. Ignore the handle.
1632			if (o.clickBar && event.target === $sb[0]) {
1633				stopDefault(event);
1634				// Calculate new handle position and sync SLIDEE to it
1635				slideTo(handleToSlidee((o.horizontal ? event.pageX - $sb.offset().left : event.pageY - $sb.offset().top) - handleSize / 2));
1636			}
1637		}
1638
1639		/**
1640		 * Keyboard input handler.
1641		 *
1642		 * @param  {Event} event
1643		 *
1644		 * @return {Void}
1645		 */
1646		function keyboardHandler(event) {
1647			if (!o.keyboardNavBy) {
1648				return;
1649			}
1650
1651			switch (event.which) {
1652				// Left or Up
1653				case o.horizontal ? 37 : 38:
1654					stopDefault(event);
1655					self[o.keyboardNavBy === 'pages' ? 'prevPage' : 'prev']();
1656					break;
1657
1658				// Right or Down
1659				case o.horizontal ? 39 : 40:
1660					stopDefault(event);
1661					self[o.keyboardNavBy === 'pages' ? 'nextPage' : 'next']();
1662					break;
1663			}
1664		}
1665
1666		/**
1667		 * Click on item activation handler.
1668		 *
1669		 * @param  {Event} event
1670		 *
1671		 * @return {Void}
1672		 */
1673		function activateHandler(event) {
1674			/*jshint validthis:true */
1675
1676			// Ignore clicks on interactive elements.
1677			if (isInteractive(this)) {
1678				event.originalEvent[namespace + 'ignore'] = true;
1679				return;
1680			}
1681
1682			// Ignore events that:
1683			// - are not originating from direct SLIDEE children
1684			// - originated from interactive elements
1685			if (this.parentNode !== $slidee[0] || event.originalEvent[namespace + 'ignore']) return;
1686
1687			self.activate(this);
1688		}
1689
1690		/**
1691		 * Click on page button handler.
1692		 *
1693		 * @param {Event} event
1694		 *
1695		 * @return {Void}
1696		 */
1697		function activatePageHandler() {
1698			/*jshint validthis:true */
1699			// Accept only events from direct pages bar children.
1700			if (this.parentNode === $pb[0]) {
1701				self.activatePage($pages.index(this));
1702			}
1703		}
1704
1705		/**
1706		 * Pause on hover handler.
1707		 *
1708		 * @param  {Event} event
1709		 *
1710		 * @return {Void}
1711		 */
1712		function pauseOnHoverHandler(event) {
1713			if (o.pauseOnHover) {
1714				self[event.type === 'mouseenter' ? 'pause' : 'resume'](2);
1715			}
1716		}
1717
1718		/**
1719		 * Trigger callbacks for event.
1720		 *
1721		 * @param  {String} name Event name.
1722		 * @param  {Mixed}  argX Arguments passed to callbacks.
1723		 *
1724		 * @return {Void}
1725		 */
1726		function trigger(name, arg1) {
1727			if (callbacks[name]) {
1728				l = callbacks[name].length;
1729				// Callbacks will be stored and executed from a temporary array to not
1730				// break the execution queue when one of the callbacks unbinds itself.
1731				tmpArray.length = 0;
1732				for (i = 0; i < l; i++) {
1733					tmpArray.push(callbacks[name][i]);
1734				}
1735				// Execute the callbacks
1736				for (i = 0; i < l; i++) {
1737					tmpArray[i].call(self, name, arg1);
1738				}
1739			}
1740		}
1741
1742		/**
1743		 * Destroys instance and everything it created.
1744		 *
1745		 * @return {Void}
1746		 */
1747		self.destroy = function () {
1748			// Remove the reference to itself
1749			Sly.removeInstance(frame);
1750
1751			// Unbind all events
1752			$scrollSource
1753				.add($handle)
1754				.add($sb)
1755				.add($pb)
1756				.add($forwardButton)
1757				.add($backwardButton)
1758				.add($prevButton)
1759				.add($nextButton)
1760				.add($prevPageButton)
1761				.add($nextPageButton)
1762				.off('.' + namespace);
1763
1764			// Unbinding specifically as to not nuke out other instances
1765			$doc.off('keydown', keyboardHandler);
1766
1767			// Remove classes
1768			$prevButton
1769				.add($nextButton)
1770				.add($prevPageButton)
1771				.add($nextPageButton)
1772				.removeClass(o.disabledClass);
1773
1774			if ($items && rel.activeItem != null) {
1775				$items.eq(rel.activeItem).removeClass(o.activeClass);
1776			}
1777
1778			// Remove page items
1779			$pb.empty();
1780
1781			if (!parallax) {
1782				// Unbind events from frame
1783				$frame.off('.' + namespace);
1784				// Restore original styles
1785				frameStyles.restore();
1786				slideeStyles.restore();
1787				sbStyles.restore();
1788				handleStyles.restore();
1789				// Remove the instance from element data storage
1790				$.removeData(frame, namespace);
1791			}
1792
1793			// Clean up collections
1794			items.length = pages.length = 0;
1795			last = {};
1796
1797			// Reset initialized status and return the instance
1798			self.initialized = 0;
1799			return self;
1800		};
1801
1802		/**
1803		 * Initialize.
1804		 *
1805		 * @return {Object}
1806		 */
1807		self.init = function () {
1808			if (self.initialized) {
1809				return;
1810			}
1811
1812			// Disallow multiple instances on the same element
1813			if (Sly.getInstance(frame)) throw new Error('There is already a Sly instance on this element');
1814
1815			// Store the reference to itself
1816			Sly.storeInstance(frame, self);
1817
1818			// Register callbacks map
1819			self.on(callbackMap);
1820
1821			// Save styles
1822			var holderProps = ['overflow', 'position'];
1823			var movableProps = ['position', 'webkitTransform', 'msTransform', 'transform', 'left', 'top', 'width', 'height'];
1824			frameStyles.save.apply(frameStyles, holderProps);
1825			sbStyles.save.apply(sbStyles, holderProps);
1826			slideeStyles.save.apply(slideeStyles, movableProps);
1827			handleStyles.save.apply(handleStyles, movableProps);
1828
1829			// Set required styles
1830			var $movables = $handle;
1831			if (!parallax) {
1832				$movables = $movables.add($slidee);
1833				$frame.css('overflow', 'hidden');
1834				if (!transform && $frame.css('position') === 'static') {
1835					$frame.css('position', 'relative');
1836				}
1837			}
1838			if (transform) {
1839				if (gpuAcceleration) {
1840					$movables.css(transform, gpuAcceleration);
1841				}
1842			} else {
1843				if ($sb.css('position') === 'static') {
1844					$sb.css('position', 'relative');
1845				}
1846				$movables.css({ position: 'absolute' });
1847			}
1848
1849			// Navigation buttons
1850			if (o.forward) {
1851				$forwardButton.on(mouseDownEvent, buttonsHandler);
1852			}
1853			if (o.backward) {
1854				$backwardButton.on(mouseDownEvent, buttonsHandler);
1855			}
1856			if (o.prev) {
1857				$prevButton.on(clickEvent, buttonsHandler);
1858			}
1859			if (o.next) {
1860				$nextButton.on(clickEvent, buttonsHandler);
1861			}
1862			if (o.prevPage) {
1863				$prevPageButton.on(clickEvent, buttonsHandler);
1864			}
1865			if (o.nextPage) {
1866				$nextPageButton.on(clickEvent, buttonsHandler);
1867			}
1868
1869			// Scrolling navigation
1870			$scrollSource.on(wheelEvent, scrollHandler);
1871
1872			// Clicking on scrollbar navigation
1873			if ($sb[0]) {
1874				$sb.on(clickEvent, scrollbarHandler);
1875			}
1876
1877			// Click on items navigation
1878			if (itemNav && o.activateOn) {
1879				$frame.on(o.activateOn + '.' + namespace, '*', activateHandler);
1880			}
1881
1882			// Pages navigation
1883			if ($pb[0] && o.activatePageOn) {
1884				$pb.on(o.activatePageOn + '.' + namespace, '*', activatePageHandler);
1885			}
1886
1887			// Dragging navigation
1888			$dragSource.on(dragInitEvents, { source: 'slidee' }, dragInit);
1889
1890			// Scrollbar dragging navigation
1891			if ($handle) {
1892				$handle.on(dragInitEvents, { source: 'handle' }, dragInit);
1893			}
1894
1895			// Keyboard navigation
1896			$doc.on('keydown', keyboardHandler);
1897
1898			if (!parallax) {
1899				// Pause on hover
1900				$frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler);
1901				// Reset native FRAME element scroll
1902				$frame.on('scroll.' + namespace, resetScroll);
1903			}
1904
1905			// Mark instance as initialized
1906			self.initialized = 1;
1907
1908			// Load
1909			load(true);
1910
1911			// Initiate automatic cycling
1912			if (o.cycleBy && !parallax) {
1913				self[o.startPaused ? 'pause' : 'resume']();
1914			}
1915
1916			// Return instance
1917			return self;
1918		};
1919	}
1920
1921	Sly.getInstance = function (element) {
1922		return $.data(element, namespace);
1923	};
1924
1925	Sly.storeInstance = function (element, sly) {
1926		return $.data(element, namespace, sly);
1927	};
1928
1929	Sly.removeInstance = function (element) {
1930		return $.removeData(element, namespace);
1931	};
1932
1933	/**
1934	 * Return type of the value.
1935	 *
1936	 * @param  {Mixed} value
1937	 *
1938	 * @return {String}
1939	 */
1940	function type(value) {
1941		if (value == null) {
1942			return String(value);
1943		}
1944
1945		if (typeof value === 'object' || typeof value === 'function') {
1946			return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object';
1947		}
1948
1949		return typeof value;
1950	}
1951
1952	/**
1953	 * Event preventDefault & stopPropagation helper.
1954	 *
1955	 * @param {Event} event     Event object.
1956	 * @param {Bool}  noBubbles Cancel event bubbling.
1957	 *
1958	 * @return {Void}
1959	 */
1960	function stopDefault(event, noBubbles) {
1961		event.preventDefault();
1962		if (noBubbles) {
1963			event.stopPropagation();
1964		}
1965	}
1966
1967	/**
1968	 * Disables an event it was triggered on and unbinds itself.
1969	 *
1970	 * @param  {Event} event
1971	 *
1972	 * @return {Void}
1973	 */
1974	function disableOneEvent(event) {
1975		/*jshint validthis:true */
1976		stopDefault(event, 1);
1977		$(this).off(event.type, disableOneEvent);
1978	}
1979
1980	/**
1981	 * Resets native element scroll values to 0.
1982	 *
1983	 * @return {Void}
1984	 */
1985	function resetScroll() {
1986		/*jshint validthis:true */
1987		this.scrollLeft = 0;
1988		this.scrollTop = 0;
1989	}
1990
1991	/**
1992	 * Check if variable is a number.
1993	 *
1994	 * @param {Mixed} value
1995	 *
1996	 * @return {Boolean}
1997	 */
1998	function isNumber(value) {
1999		return !isNaN(parseFloat(value)) && isFinite(value);
2000	}
2001
2002	/**
2003	 * Parse style to pixels.
2004	 *
2005	 * @param {Object}   $item    jQuery object with element.
2006	 * @param {Property} property CSS property to get the pixels from.
2007	 *
2008	 * @return {Int}
2009	 */
2010	function getPx($item, property) {
2011		return 0 | round(String($item.css(property)).replace(/[^\-0-9.]/g, ''));
2012	}
2013
2014	/**
2015	 * Make sure that number is within the limits.
2016	 *
2017	 * @param {Number} number
2018	 * @param {Number} min
2019	 * @param {Number} max
2020	 *
2021	 * @return {Number}
2022	 */
2023	function within(number, min, max) {
2024		return number < min ? min : number > max ? max : number;
2025	}
2026
2027	/**
2028	 * Saves element styles for later restoration.
2029	 *
2030	 * Example:
2031	 *   var styles = new StyleRestorer(frame);
2032	 *   styles.save('position');
2033	 *   element.style.position = 'absolute';
2034	 *   styles.restore(); // restores to state before the assignment above
2035	 *
2036	 * @param {Element} element
2037	 */
2038	function StyleRestorer(element) {
2039		var self = {};
2040		self.style = {};
2041		self.save = function () {
2042			if (!element || !element.nodeType) return;
2043			for (var i = 0; i < arguments.length; i++) {
2044				self.style[arguments[i]] = element.style[arguments[i]];
2045			}
2046			return self;
2047		};
2048		self.restore = function () {
2049			if (!element || !element.nodeType) return;
2050			for (var prop in self.style) {
2051				if (self.style.hasOwnProperty(prop)) element.style[prop] = self.style[prop];
2052			}
2053			return self;
2054		};
2055		return self;
2056	}
2057
2058	// Local WindowAnimationTiming interface polyfill
2059	(function (w) {
2060		rAF = w.requestAnimationFrame
2061			|| w.webkitRequestAnimationFrame
2062			|| fallback;
2063
2064		/**
2065		* Fallback implementation.
2066		*/
2067		var prev = new Date().getTime();
2068		function fallback(fn) {
2069			var curr = new Date().getTime();
2070			var ms = Math.max(0, 16 - (curr - prev));
2071			var req = setTimeout(fn, ms);
2072			prev = curr;
2073			return req;
2074		}
2075
2076		/**
2077		* Cancel.
2078		*/
2079		var cancel = w.cancelAnimationFrame
2080			|| w.webkitCancelAnimationFrame
2081			|| w.clearTimeout;
2082
2083		cAF = function(id){
2084			cancel.call(w, id);
2085		};
2086	}(window));
2087
2088	// Feature detects
2089	(function () {
2090		var prefixes = ['', 'Webkit', 'Moz', 'ms', 'O'];
2091		var el = document.createElement('div');
2092
2093		function testProp(prop) {
2094			for (var p = 0, pl = prefixes.length; p < pl; p++) {
2095				var prefixedProp = prefixes[p] ? prefixes[p] + prop.charAt(0).toUpperCase() + prop.slice(1) : prop;
2096				if (el.style[prefixedProp] != null) {
2097					return prefixedProp;
2098				}
2099			}
2100		}
2101
2102		// Global support indicators
2103		transform = testProp('transform');
2104		gpuAcceleration = testProp('perspective') ? 'translateZ(0) ' : '';
2105	}());
2106
2107	// Expose class globally
2108	w[className] = Sly;
2109
2110	// jQuery proxy
2111	$.fn[pluginName] = function (options, callbackMap) {
2112		var method, methodArgs;
2113
2114		// Attributes logic
2115		if (!$.isPlainObject(options)) {
2116			if (type(options) === 'string' || options === false) {
2117				method = options === false ? 'destroy' : options;
2118				methodArgs = Array.prototype.slice.call(arguments, 1);
2119			}
2120			options = {};
2121		}
2122
2123		// Apply to all elements
2124		return this.each(function (i, element) {
2125			// Call with prevention against multiple instantiations
2126			var plugin = Sly.getInstance(element);
2127
2128			if (!plugin && !method) {
2129				// Create a new object if it doesn't exist yet
2130				plugin = new Sly(element, options, callbackMap).init();
2131			} else if (plugin && method) {
2132				// Call method
2133				if (plugin[method]) {
2134					plugin[method].apply(plugin, methodArgs);
2135				}
2136			}
2137		});
2138	};
2139
2140	// Default options
2141	Sly.defaults = {
2142		slidee:     null,  // Selector, DOM element, or jQuery object with DOM element representing SLIDEE.
2143		horizontal: false, // Switch to horizontal mode.
2144
2145		// Item based navigation
2146		itemNav:        null,  // Item navigation type. Can be: 'basic', 'centered', 'forceCentered'.
2147		itemSelector:   null,  // Select only items that match this selector.
2148		smart:          false, // Repositions the activated item to help with further navigation.
2149		activateOn:     null,  // Activate an item on this event. Can be: 'click', 'mouseenter', ...
2150		activateMiddle: false, // Always activate the item in the middle of the FRAME. forceCentered only.
2151
2152		// Scrolling
2153		scrollSource: null,  // Element for catching the mouse wheel scrolling. Default is FRAME.
2154		scrollBy:     0,     // Pixels or items to move per one mouse scroll. 0 to disable scrolling.
2155		scrollHijack: 300,   // Milliseconds since last wheel event after which it is acceptable to hijack global scroll.
2156		scrollTrap:   false, // Don't bubble scrolling when hitting scrolling limits.
2157
2158		// Dragging
2159		dragSource:    null,  // Selector or DOM element for catching dragging events. Default is FRAME.
2160		mouseDragging: false, // Enable navigation by dragging the SLIDEE with mouse cursor.
2161		touchDragging: false, // Enable navigation by dragging the SLIDEE with touch events.
2162		releaseSwing:  false, // Ease out on dragging swing release.
2163		swingSpeed:    0.2,   // Swing synchronization speed, where: 1 = instant, 0 = infinite.
2164		elasticBounds: false, // Stretch SLIDEE position limits when dragging past FRAME boundaries.
2165		dragThreshold: 3,     // Distance in pixels before Sly recognizes dragging.
2166		interactive:   null,  // Selector for special interactive elements.
2167
2168		// Scrollbar
2169		scrollBar:     null,  // Selector or DOM element for scrollbar container.
2170		dragHandle:    false, // Whether the scrollbar handle should be draggable.
2171		dynamicHandle: false, // Scrollbar handle represents the ratio between hidden and visible content.
2172		minHandleSize: 50,    // Minimal height or width (depends on sly direction) of a handle in pixels.
2173		clickBar:      false, // Enable navigation by clicking on scrollbar.
2174		syncSpeed:     0.5,   // Handle =>
2174 SLIDEE synchronization speed, where: 1 = instant, 0 = infinite.
2175
2176		// Pagesbar
2177		pagesBar:       null, // Selector or DOM element for pages bar container.
2178		activatePageOn: null, // Event used to activate page. Can be: click, mouseenter, ...
2179		pageBuilder:          // Page item generator.
2180			function (index) {
2181				return '<li>' + (index + 1) + '</li>';
2182			},
2183
2184		// Navigation buttons
2185		forward:  null, // Selector or DOM element for "forward movement" button.
2186		backward: null, // Selector or DOM element for "backward movement" button.
2187		prev:     null, // Selector or DOM element for "previous item" button.
2188		next:     null, // Selector or DOM element for "next item" button.
2189		prevPage: null, // Selector or DOM element for "previous page" button.
2190		nextPage: null, // Selector or DOM element for "next page" button.
2191
2192		// Automated cycling
2193		cycleBy:       null,  // Enable automatic cycling by 'items' or 'pages'.
2194		cycleInterval: 5000,  // Delay between cycles in milliseconds.
2195		pauseOnHover:  false, // Pause cycling when mouse hovers over the FRAME.
2196		startPaused:   false, // Whether to start in paused sate.
2197
2198		// Mixed options
2199		moveBy:        300,     // Speed in pixels per second used by forward and backward buttons.
2200		speed:         0,       // Animations speed in milliseconds. 0 to disable animations.
2201		easing:        'swing', // Easing for duration based (tweening) animations.
2202		startAt:       null,    // Starting offset in pixels or items.
2203		keyboardNavBy: null,    // Enable keyboard navigation by 'items' or 'pages'.
2204
2205		// Classes
2206		draggedClass:  'dragged', // Class for dragged elements (like SLIDEE or scrollbar handle).
2207		activeClass:   'active',  // Class for active items and pages.
2208		disabledClass: 'disabled' // Class for disabled navigation elements.
2209	};
2210}(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.