PageSourceSearch

https://aast.edu/template/50yrs-slider/js/iscroll.js

js aast.edu collected 2026-09-24 09:20:51 UTC 55,742 bytes, 2,197 lines download raw bytes

1/*! iScroll v5.2.0-snapshot ~ (c) 2008-2017 Matteo Spinelli ~ http://cubiq.org/license */
2(function (window, document, Math) {
3var rAF = window.requestAnimationFrame	||
4	window.webkitRequestAnimationFrame	||
5	window.mozRequestAnimationFrame		||
6	window.oRequestAnimationFrame		||
7	window.msRequestAnimationFrame		||
8	function (callback) { window.setTimeout(callback, 1000 / 60); };
9
10var utils = (function () {
11	var me = {};
12
13	var _elementStyle = document.createElement('div').style;
14	var _vendor = (function () {
15		var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'],
16			transform,
17			i = 0,
18			l = vendors.length;
19
20		for ( ; i < l; i++ ) {
21			transform = vendors[i] + 'ransform';
22			if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1);
23		}
24
25		return false;
26	})();
27
28	function _prefixStyle (style) {
29		if ( _vendor === false ) return false;
30		if ( _vendor === '' ) return style;
31		return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
32	}
33
34	me.getTime = Date.now || function getTime () { return new Date().getTime(); };
35
36	me.extend = function (target, obj) {
37		for ( var i in obj ) {
38			target[i] = obj[i];
39		}
40	};
41
42	me.addEvent = function (el, type, fn, capture) {
43		el.addEventListener(type, fn, !!capture);
44	};
45
46	me.removeEvent = function (el, type, fn, capture) {
47		el.removeEventListener(type, fn, !!capture);
48	};
49
50	me.prefixPointerEvent = function (pointerEvent) {
51		return window.MSPointerEvent ?
52			'MSPointer' + pointerEvent.charAt(7).toUpperCase() + pointerEvent.substr(8):
53			pointerEvent;
54	};
55
56	me.momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) {
57		var distance = current - start,
58			speed = Math.abs(distance) / time,
59			destination,
60			duration;
61
62		deceleration = deceleration === undefined ? 0.0006 : deceleration;
63
64		destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
65		duration = speed / deceleration;
66
67		if ( destination < lowerMargin ) {
68			destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
69			distance = Math.abs(destination - current);
70			duration = distance / speed;
71		} else if ( destination > 0 ) {
72			destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
73			distance = Math.abs(current) + destination;
74			duration = distance / speed;
75		}
76
77		return {
78			destination: Math.round(destination),
79			duration: duration
80		};
81	};
82
83	var _transform = _prefixStyle('transform');
84
85	me.extend(me, {
86		hasTransform: _transform !== false,
87		hasPerspective: _prefixStyle('perspective') in _elementStyle,
88		hasTouch: 'ontouchstart' in window,
89		hasPointer: !!(window.PointerEvent || window.MSPointerEvent), // IE10 is prefixed
90		hasTransition: _prefixStyle('transition') in _elementStyle
91	});
92
93	/*
94	This should find all Android browsers lower than build 535.19 (both stock browser and webview)
95	- galaxy S2 is ok
96    - 2.3.6 : `AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1`
97    - 4.0.4 : `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30`
98   - galaxy S3 is badAndroid (stock brower, webview)
99     `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30`
100   - galaxy S4 is badAndroid (stock brower, webview)
101     `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30`
102   - galaxy S5 is OK
103     `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)`
104   - galaxy S6 is OK
105     `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)`
106  */
107	me.isBadAndroid = (function() {
108		var appVersion = window.navigator.appVersion;
109		// Android browser is not a chrome browser.
110		if (/Android/.test(appVersion) && !(/Chrome\/\d/.test(appVersion))) {
111			var safariVersion = appVersion.match(/Safari\/(\d+.\d)/);
112			if(safariVersion && typeof safariVersion === "object" && safariVersion.length >= 2) {
113				return parseFloat(safariVersion[1]) < 535.19;
114			} else {
115				return true;
116			}
117		} else {
118			return false;
119		}
120	})();
121
122	me.extend(me.style = {}, {
123		transform: _transform,
124		transitionTimingFunction: _prefixStyle('transitionTimingFunction'),
125		transitionDuration: _prefixStyle('transitionDuration'),
126		transitionDelay: _prefixStyle('transitionDelay'),
127		transformOrigin: _prefixStyle('transformOrigin'),
128		touchAction: _prefixStyle('touchAction')
129	});
130
131	me.hasClass = function (e, c) {
132		var re = new RegExp("(^|\\s)" + c + "(\\s|$)");
133		return re.test(e.className);
134	};
135
136	me.addClass = function (e, c) {
137		if ( me.hasClass(e, c) ) {
138			return;
139		}
140
141		var newclass = e.className.split(' ');
142		newclass.push(c);
143		e.className = newclass.join(' ');
144	};
145
146	me.removeClass = function (e, c) {
147		if ( !me.hasClass(e, c) ) {
148			return;
149		}
150
151		var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g');
152		e.className = e.className.replace(re, ' ');
153	};
154
155	me.offset = function (el) {
156		var left = -el.offsetLeft,
157			top = -el.offsetTop;
158
159		// jshint -W084
160		while (el = el.offsetParent) {
161			left -= el.offsetLeft;
162			top -= el.offsetTop;
163		}
164		// jshint +W084
165
166		return {
167			left: left,
168			top: top
169		};
170	};
171
172	me.preventDefaultException = function (el, exceptions) {
173		for ( var i in exceptions ) {
174			if ( exceptions[i].test(el[i]) ) {
175				return true;
176			}
177		}
178
179		return false;
180	};
181
182	me.extend(me.eventType = {}, {
183		touchstart: 1,
184		touchmove: 1,
185		touchend: 1,
186
187		mousedown: 2,
188		mousemove: 2,
189		mouseup: 2,
190
191		pointerdown: 3,
192		pointermove: 3,
193		pointerup: 3,
194
195		MSPointerDown: 3,
196		MSPointerMove: 3,
197		MSPointerUp: 3
198	});
199
200	me.extend(me.ease = {}, {
201		quadratic: {
202			style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
203			fn: function (k) {
204				return k * ( 2 - k );
205			}
206		},
207		circular: {
208			style: 'cubic-bezier(0.1, 0.57, 0.1, 1)',	// Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
209			fn: function (k) {
210				return Math.sqrt( 1 - ( --k * k ) );
211			}
212		},
213		back: {
214			style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
215			fn: function (k) {
216				var b = 4;
217				return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
218			}
219		},
220		bounce: {
221			style: '',
222			fn: function (k) {
223				if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
224					return 7.5625 * k * k;
225				} else if ( k < ( 2 / 2.75 ) ) {
226					return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
227				} else if ( k < ( 2.5 / 2.75 ) ) {
228					return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
229				} else {
230					return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
231				}
232			}
233		},
234		elastic: {
235			style: '',
236			fn: function (k) {
237				var f = 0.22,
238					e = 0.4;
239
240				if ( k === 0 ) { return 0; }
241				if ( k == 1 ) { return 1; }
242
243				return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
244			}
245		}
246	});
247
248	me.tap = function (e, eventName) {
249		var ev = document.createEvent('Event');
250		ev.initEvent(eventName, true, true);
251		ev.pageX = e.pageX;
252		ev.pageY = e.pageY;
253		e.target.dispatchEvent(ev);
254	};
255
256	me.click = function (e) {
257		var target = e.target,
258			ev;
259
260		if ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) {
261			// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent
262			// initMouseEvent is deprecated.
263			ev = document.createEvent(window.MouseEvent ? 'MouseEvents' : 'Event');
264			ev.initEvent('click', true, true);
265			ev.view = e.view || window;
266			ev.detail = 1;
267			ev.screenX = target.screenX || 0;
268			ev.screenY = target.screenY || 0;
269			ev.clientX = target.clientX || 0;
270			ev.clientY = target.clientY || 0;
271			ev.ctrlKey = !!e.ctrlKey;
272			ev.altKey = !!e.altKey;
273			ev.shiftKey = !!e.shiftKey;
274			ev.metaKey = !!e.metaKey;
275			ev.button = 0;
276			ev.relatedTarget = null;
277			ev._constructed = true;
278			target.dispatchEvent(ev);
279		}
280	};
281
282	me.getTouchAction = function(eventPassthrough, addPinch) {
283		var touchAction = 'none';
284		if ( eventPassthrough === 'vertical' ) {
285			touchAction = 'pan-y';
286		} else if (eventPassthrough === 'horizontal' ) {
287			touchAction = 'pan-x';
288		}
289		if (addPinch && touchAction != 'none') {
290			// add pinch-zoom support if the browser supports it, but if not (eg. Chrome <55) do nothing
291			touchAction += ' pinch-zoom';
292		}
293		return touchAction;
294	};
295
296	me.getRect = function(el) {
297		if (el instanceof SVGElement) {
298			var rect = el.getBoundingClientRect();
299			return {
300				top : rect.top,
301				left : rect.left,
302				width : rect.width,
303				height : rect.height
304			};
305		} else {
306			return {
307				top : el.offsetTop,
308				left : el.offsetLeft,
309				width : el.offsetWidth,
310				height : el.offsetHeight
311			};
312		}
313	};
314
315	return me;
316})();
317function IScroll (el, options) {
318	this.wrapper = typeof el == 'string' ? document.querySelector(el) : el;
319	this.scroller = this.wrapper.children[0];
320	this.scrollerStyle = this.scroller.style;		// cache style for better performance
321
322	this.options = {
323
324		resizeScrollbars: true,
325
326		mouseWheelSpeed: 20,
327
328		snapThreshold: 0.334,
329
330// INSERT POINT: OPTIONS
331		disablePointer : !utils.hasPointer,
332		disableTouch : utils.hasPointer || !utils.hasTouch,
333		disableMouse : utils.hasPointer || utils.hasTouch,
334		startX: 0,
335		startY: 0,
336		scrollY: true,
337		directionLockThreshold: 5,
338		momentum: true,
339
340		bounce: true,
341		bounceTime: 600,
342		bounceEasing: '',
343
344		preventDefault: true,
345		preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|A)$/ },
346
347		HWCompositing: true,
348		useTransition: true,
349		useTransform: true,
350		bindToWrapper: typeof window.onmousedown === "undefined"
351	};
352
353	for ( var i in options ) {
354		this.options[i] = options[i];
355	}
356
357	// Normalize options
358	this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : '';
359
360	this.options.useTransition = utils.hasTransition && this.options.useTransition;
361	this.options.useTransform = utils.hasTransform && this.options.useTransform;
362
363	this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough;
364	this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault;
365
366	// If you want eventPassthrough I have to lock one of the axes
367	this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY;
368	this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX;
369
370	// With eventPassthrough we also need lockDirection mechanism
371	this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough;
372	this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold;
373
374	this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing;
375
376	this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling;
377
378	if ( this.options.tap === true ) {
379		this.options.tap = 'tap';
380	}
381
382	// https://github.com/cubiq/iscroll/issues/1029
383	if (!this.options.useTransition && !this.options.useTransform) {
384		if(!(/relative|absolute/i).test(this.scrollerStyle.position)) {
385			this.scrollerStyle.position = "relative";
386		}
387	}
388
389	if ( this.options.shrinkScrollbars == 'scale' ) {
390		this.options.useTransition = false;
391	}
392
393	this.options.invertWheelDirection = this.options.invertWheelDirection ? -1 : 1;
394
395	if ( this.options.probeType == 3 ) {
396		this.options.useTransition = false;	}
397
398// INSERT POINT: NORMALIZATION
399
400	// Some defaults
401	this.x = 0;
402	this.y = 0;
403	this.directionX = 0;
404	this.directionY = 0;
405	this._events = {};
406
407// INSERT POINT: DEFAULTS
408
409	this._init();
410	this.refresh();
411
412	this.scrollTo(this.options.startX, this.options.startY);
413	this.enable();
414}
415
416IScroll.prototype = {
417	version: '5.2.0-snapshot',
418
419	_init: function () {
420		this._initEvents();
421
422		if ( this.options.scrollbars || this.options.indicators ) {
423			this._initIndicators();
424		}
425
426		if ( this.options.mouseWheel ) {
427			this._initWheel();
428		}
429
430		if ( this.options.snap ) {
431			this._initSnap();
432		}
433
434		if ( this.options.keyBindings ) {
435			this._initKeys();
436		}
437
438// INSERT POINT: _init
439
440	},
441
442	destroy: function () {
443		this._initEvents(true);
444		clearTimeout(this.resizeTimeout);
445 		this.resizeTimeout = null;
446		this._execEvent('destroy');
447	},
448
449	_transitionEnd: function (e) {
450		if ( e.target != this.scroller || !this.isInTransition ) {
451			return;
452		}
453
454		this._transitionTime();
455		if ( !this.resetPosition(this.options.bounceTime) ) {
456			this.isInTransition = false;
457			this._execEvent('scrollEnd');
458		}
459	},
460
461	_start: function (e) {
462		// React to left mouse button only
463		if ( utils.eventType[e.type] != 1 ) {
464		  // for button property
465		  // http://unixpapa.com/js/mouse.html
466		  var button;
467	    if (!e.which) {
468	      /* IE case */
469	      button = (e.button < 2) ? 0 :
470	               ((e.button == 4) ? 1 : 2);
471	    } else {
472	      /* All others */
473	      button = e.button;
474	    }
475			if ( button !== 0 ) {
476				return;
477			}
478		}
479
480		if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) {
481			return;
482		}
483
484		if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {
485			e.preventDefault();
486		}
487
488		var point = e.touches ? e.touches[0] : e,
489			pos;
490
491		this.initiated	= utils.eventType[e.type];
492		this.moved		= false;
493		this.distX		= 0;
494		this.distY		= 0;
495		this.directionX = 0;
496		this.directionY = 0;
497		this.directionLocked = 0;
498
499		this.startTime = utils.getTime();
500
501		if ( this.options.useTransition && this.isInTransition ) {
502			this._transitionTime();
503			this.isInTransition = false;
504			pos = this.getComputedPosition();
505			this._translate(Math.round(pos.x), Math.round(pos.y));
506			this._execEvent('scrollEnd');
507		} else if ( !this.options.useTransition && this.isAnimating ) {
508			this.isAnimating = false;
509			this._execEvent('scrollEnd');
510		}
511
512		this.startX    = this.x;
513		this.startY    = this.y;
514		this.absStartX = this.x;
515		this.absStartY = this.y;
516		this.pointX    = point.pageX;
517		this.pointY    = point.pageY;
518
519		this._execEvent('beforeScrollStart');
520	},
521
522	_move: function (e) {
523		if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) {
524			return;
525		}
526
527		if ( this.options.preventDefault ) {	// increases performance on Android? TODO: check!
528			e.preventDefault();
529		}
530
531		var point		= e.touches ? e.touches[0] : e,
532			deltaX		= point.pageX - this.pointX,
533			deltaY		= point.pageY - this.pointY,
534			timestamp	= utils.getTime(),
535			newX, newY,
536			absDistX, absDistY;
537
538		this.pointX		= point.pageX;
539		this.pointY		= point.pageY;
540
541		this.distX		+= deltaX;
542		this.distY		+= deltaY;
543		absDistX		= Math.abs(this.distX);
544		absDistY		= Math.abs(this.distY);
545
546		// We need to move at least 10 pixels for the scrolling to initiate
547		if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) {
548			return;
549		}
550
551		// If you are scrolling in one direction lock the other
552		if ( !this.directionLocked && !this.options.freeScroll ) {
553			if ( absDistX > absDistY + this.options.directionLockThreshold ) {
554				this.directionLocked = 'h';		// lock horizontally
555			} else if ( absDistY >= absDistX + this.options.directionLockThreshold ) {
556				this.directionLocked = 'v';		// lock vertically
557			} else {
558				this.directionLocked = 'n';		// no lock
559			}
560		}
561
562		if ( this.directionLocked == 'h' ) {
563			if ( this.options.eventPassthrough == 'vertical' ) {
564				e.preventDefault();
565			} else if ( this.options.eventPassthrough == 'horizontal' ) {
566				this.initiated = false;
567				return;
568			}
569
570			deltaY = 0;
571		} else if ( this.directionLocked == 'v' ) {
572			if ( this.options.eventPassthrough == 'horizontal' ) {
573				e.preventDefault();
574			} else if ( this.options.eventPassthrough == 'vertical' ) {
575				this.initiated = false;
576				return;
577			}
578
579			deltaX = 0;
580		}
581
582		deltaX = this.hasHorizontalScroll ? deltaX : 0;
583		deltaY = this.hasVerticalScroll ? deltaY : 0;
584
585		newX = this.x + deltaX;
586		newY = this.y + deltaY;
587
588		// Slow down if outside of the boundaries
589		if ( newX > 0 || newX < this.maxScrollX ) {
590			newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX;
591		}
592		if ( newY > 0 || newY < this.maxScrollY ) {
593			newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY;
594		}
595
596		this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
597		this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
598
599		if ( !this.moved ) {
600			this._execEvent('scrollStart');
601		}
602
603		this.moved = true;
604
605		this._translate(newX, newY);
606
607/* REPLACE START: _move */
608		if ( timestamp - this.startTime > 300 ) {
609			this.startTime = timestamp;
610			this.startX = this.x;
611			this.startY = this.y;
612
613			if ( this.options.probeType == 1 ) {
614				this._execEvent('scroll');
615			}
616		}
617
618		if ( this.options.probeType > 1 ) {
619			this._execEvent('scroll');
620		}
621/* REPLACE END: _move */
622
623	},
624
625	_end: function (e) {
626		if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) {
627			return;
628		}
629
630		if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {
631			e.preventDefault();
632		}
633
634		var point = e.changedTouches ? e.changedTouches[0] : e,
635			momentumX,
636			momentumY,
637			duration = utils.getTime() - this.startTime,
638			newX = Math.round(this.x),
639			newY = Math.round(this.y),
640			distanceX = Math.abs(newX - this.startX),
641			distanceY = Math.abs(newY - this.startY),
642			time = 0,
643			easing = '';
644
645		this.isInTransition = 0;
646		this.initiated = 0;
647		this.endTime = utils.getTime();
648
649		// reset if we are outside of the boundaries
650		if ( this.resetPosition(this.options.bounceTime) ) {
651			return;
652		}
653
654		this.scrollTo(newX, newY);	// ensures that the last position is rounded
655
656		// we scrolled less than 10 pixels
657		if ( !this.moved ) {
658			if ( this.options.tap ) {
659				utils.tap(e, this.options.tap);
660			}
661
662			if ( this.options.click ) {
663				utils.click(e);
664			}
665
666			this._execEvent('scrollCancel');
667			return;
668		}
669
670		if ( this._events.flick && duration < 200 && distanceX < 100 && distanceY < 100 ) {
671			this._execEvent('flick');
672			return;
673		}
674
675		// start momentum animation if needed
676		if ( this.options.momentum && duration < 300 ) {
677			momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : { destination: newX, duration: 0 };
678			momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : { destination: newY, duration: 0 };
679			newX = momentumX.destination;
680			newY = momentumY.destination;
681			time = Math.max(momentumX.duration, momentumY.duration);
682			this.isInTransition = 1;
683		}
684
685
686		if ( this.options.snap ) {
687			var snap = this._nearestSnap(newX, newY);
688			this.currentPage = snap;
689			time = this.options.snapSpeed || Math.max(
690					Math.max(
691						Math.min(Math.abs(newX - snap.x), 1000),
692						Math.min(Math.abs(newY - snap.y), 1000)
693					), 300);
694			newX = snap.x;
695			newY = snap.y;
696
697			this.directionX = 0;
698			this.directionY = 0;
699			easing = this.options.bounceEasing;
700		}
701
702// INSERT POINT: _end
703
704		if ( newX != this.x || newY != this.y ) {
705			// change easing function when scroller goes out of the boundaries
706			if ( newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY ) {
707				easing = utils.ease.quadratic;
708			}
709
710			this.scrollTo(newX, newY, time, easing);
711			return;
712		}
713
714		this._execEvent('scrollEnd');
715	},
716
717	_resize: function () {
718		var that = this;
719
720		clearTimeout(this.resizeTimeout);
721
722		this.resizeTimeout = setTimeout(function () {
723			that.refresh();
724		}, this.options.resizePolling);
725	},
726
727	resetPosition: function (time) {
728		var x = this.x,
729			y = this.y;
730
731		time = time || 0;
732
733		if ( !this.hasHorizontalScroll || this.x > 0 ) {
734			x = 0;
735		} else if ( this.x < this.maxScrollX ) {
736			x = this.maxScrollX;
737		}
738
739		if ( !this.hasVerticalScroll || this.y > 0 ) {
740			y = 0;
741		} else if ( this.y < this.maxScrollY ) {
742			y = this.maxScrollY;
743		}
744
745		if ( x == this.x && y == this.y ) {
746			return false;
747		}
748
749		this.scrollTo(x, y, time, this.options.bounceEasing);
750
751		return true;
752	},
753
754	disable: function () {
755		this.enabled = false;
756	},
757
758	enable: function () {
759		this.enabled = true;
760	},
761
762	refresh: function () {
763		utils.getRect(this.wrapper);		// Force reflow
764
765		this.wrapperWidth	= this.wrapper.clientWidth;
766		this.wrapperHeight	= this.wrapper.clientHeight;
767
768		var rect = utils.getRect(this.scroller);
769/* REPLACE START: refresh */
770
771		this.scrollerWidth	= rect.width;
772		this.scrollerHeight	= rect.height;
773
774		this.maxScrollX		= this.wrapperWidth - this.scrollerWidth;
775		this.maxScrollY		= this.wrapperHeight - this.scrollerHeight;
776
777/* REPLACE END: refresh */
778
779		this.hasHorizontalScroll	= this.options.scrollX && this.maxScrollX < 0;
780		this.hasVerticalScroll		= this.options.scrollY && this.maxScrollY < 0;
781		
782		if ( !this.hasHorizontalScroll ) {
783			this.maxScrollX = 0;
784			this.scrollerWidth = this.wrapperWidth;
785		}
786
787		if ( !this.hasVerticalScroll ) {
788			this.maxScrollY = 0;
789			this.scrollerHeight = this.wrapperHeight;
790		}
791
792		this.endTime = 0;
793		this.directionX = 0;
794		this.directionY = 0;
795		
796		if(utils.hasPointer && !this.options.disablePointer) {
797			// The wrapper should have `touchAction` property for using pointerEvent.
798			this.wrapper.style[utils.style.touchAction] = utils.getTouchAction(this.options.eventPassthrough, true);
799
800			// case. not support 'pinch-zoom'
801			// https://github.com/cubiq/iscroll/issues/1118#issuecomment-270057583
802			if (!this.wrapper.style[utils.style.touchAction]) {
803				this.wrapper.style[utils.style.touchAction] = utils.getTouchAction(this.options.eventPassthrough, false);
804			}
805		}
806		this.wrapperOffset = utils.offset(this.wrapper);
807
808		this._execEvent('refresh');
809
810		this.resetPosition();
811
812// INSERT POINT: _refresh
813
814	},	
815
816	on: function (type, fn) {
817		if ( !this._events[type] ) {
818			this._events[type] = [];
819		}
820
821		this._events[type].push(fn);
822	},
823
824	off: function (type, fn) {
825		if ( !this._events[type] ) {
826			return;
827		}
828
829		var index = this._events[type].indexOf(fn);
830
831		if ( index > -1 ) {
832			this._events[type].splice(index, 1);
833		}
834	},
835
836	_execEvent: function (type) {
837		if ( !this._events[type] ) {
838			return;
839		}
840
841		var i = 0,
842			l = this._events[type].length;
843
844		if ( !l ) {
845			return;
846		}
847
848		for ( ; i < l; i++ ) {
849			this._events[type][i].apply(this, [].slice.call(arguments, 1));
850		}
851	},
852
853	scrollBy: function (x, y, time, easing) {
854		x = this.x + x;
855		y = this.y + y;
856		time = time || 0;
857
858		this.scrollTo(x, y, time, easing);
859	},
860
861	scrollTo: function (x, y, time, easing) {
862		easing = easing || utils.ease.circular;
863
864		this.isInTransition = this.options.useTransition && time > 0;
865		var transitionType = this.options.useTransition && easing.style;
866		if ( !time || transitionType ) {
867				if(transitionType) {
868					this._transitionTimingFunction(easing.style);
869					this._transitionTime(time);
870				}
871			this._translate(x, y);
872		} else {
873			this._animate(x, y, time, easing.fn);
874		}
875	},
876
877	scrollToElement: function (el, time, offsetX, offsetY, easing) {
878		el = el.nodeType ? el : this.scroller.querySelector(el);
879
880		if ( !el ) {
881			return;
882		}
883
884		var pos = utils.offset(el);
885
886		pos.left -= this.wrapperOffset.left;
887		pos.top  -= this.wrapperOffset.top;
888
889		// if offsetX/Y are true we center the element to the screen
890		var elRect = utils.getRect(el);
891		var wrapperRect = utils.getRect(this.wrapper);
892		if ( offsetX === true ) {
893			offsetX = Math.round(elRect.width / 2 - wrapperRect.width / 2);
894		}
895		if ( offsetY === true ) {
896			offsetY = Math.round(elRect.height / 2 - wrapperRect.height / 2);
897		}
898
899		pos.left -= offsetX || 0;
900		pos.top  -= offsetY || 0;
901
902		pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left;
903		pos.top  = pos.top  > 0 ? 0 : pos.top  < this.maxScrollY ? this.maxScrollY : pos.top;
904
905		time = time === undefined || time === null || time === 'auto' ? Math.max(Math.abs(this.x-pos.left), Math.abs(this.y-pos.top)) : time;
906
907		this.scrollTo(pos.left, pos.top, time, easing);
908	},
909
910	_transitionTime: function (time) {
911		if (!this.options.useTransition) {
912			return;
913		}
914		time = time || 0;
915		var durationProp = utils.style.transitionDuration;
916		if(!durationProp) {
917			return;
918		}
919
920		this.scrollerStyle[durationProp] = time + 'ms';
921
922		if ( !time && utils.isBadAndroid ) {
923			this.scrollerStyle[durationProp] = '0.0001ms';
924			// remove 0.0001ms
925			var self = this;
926			rAF(function() {
927				if(self.scrollerStyle[durationProp] === '0.0001ms') {
928					self.scrollerStyle[durationProp] = '0s';
929				}
930			});
931		}
932
933
934		if ( this.indicators ) {
935			for ( var i = this.indicators.length; i--; ) {
936				this.indicators[i].transitionTime(time);
937			}
938		}
939
940
941// INSERT POINT: _transitionTime
942
943	},
944
945	_transitionTimingFunction: function (easing) {
946		this.scrollerStyle[utils.style.transitionTimingFunction] = easing;
947
948
949		if ( this.indicators ) {
950			for ( var i = this.indicators.length; i--; ) {
951				this.indicators[i].transitionTimingFunction(easing);
952			}
953		}
954
955
956// INSERT POINT: _transitionTimingFunction
957
958	},
959
960	_translate: function (x, y) {
961		if ( this.options.useTransform ) {
962
963/* REPLACE START: _translate */
964
965			this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ;
966
967/* REPLACE END: _translate */
968
969		} else {
970			x = Math.round(x);
971			y = Math.round(y);
972			this.scrollerStyle.left = x + 'px';
973			this.scrollerStyle.top = y + 'px';
974		}
975
976		this.x = x;
977		this.y = y;
978
979
980	if ( this.indicators ) {
981		for ( var i = this.indicators.length; i--; ) {
982			this.indicators[i].updatePosition();
983		}
984	}
985
986
987// INSERT POINT: _translate
988
989	},
990
991	_initEvents: function (remove) {
992		var eventType = remove ? utils.removeEvent : utils.addEvent,
993			target = this.options.bindToWrapper ? this.wrapper : window;
994
995		eventType(window, 'orientationchange', this);
996		eventType(window, 'resize', this);
997
998		if ( this.options.click ) {
999			eventType(this.wrapper, 'click', this, true);
1000		}
1001
1002		if ( !this.options.disableMouse ) {
1003			eventType(this.wrapper, 'mousedown', this);
1004			eventType(target, 'mousemove', this);
1005			eventType(target, 'mousecancel', this);
1006			eventType(target, 'mouseup', this);
1007		}
1008
1009		if ( utils.hasPointer && !this.options.disablePointer ) {
1010			eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this);
1011			eventType(target, utils.prefixPointerEvent('pointermove'), this);
1012			eventType(target, utils.prefixPointerEvent('pointercancel'), this);
1013			eventType(target, utils.prefixPointerEvent('pointerup'), this);
1014		}
1015
1016		if ( utils.hasTouch && !this.options.disableTouch ) {
1017			eventType(this.wrapper, 'touchstart', this);
1018			eventType(target, 'touchmove', this);
1019			eventType(target, 'touchcancel', this);
1020			eventType(target, 'touchend', this);
1021		}
1022
1023		eventType(this.scroller, 'transitionend', this);
1024		eventType(this.scroller, 'webkitTransitionEnd', this);
1025		eventType(this.scroller, 'oTransitionEnd', this);
1026		eventType(this.scroller, 'MSTransitionEnd', this);
1027	},
1028
1029	getComputedPosition: function () {
1030		var matrix = window.getComputedStyle(this.scroller, null),
1031			x, y;
1032
1033		if ( this.options.useTransform ) {
1034			matrix = matrix[utils.style.transform].split(')')[0].split(', ');
1035			x = +(matrix[12] || matrix[4]);
1036			y = +(matrix[13] || matrix[5]);
1037		} else {
1038			x = +matrix.left.replace(/[^-\d.]/g, '');
1039			y = +matrix.top.replace(/[^-\d.]/g, '');
1040		}
1041
1042		return { x: x, y: y };
1043	},
1044	_initIndicators: function () {
1045		var interactive = this.options.interactiveScrollbars,
1046			customStyle = typeof this.options.scrollbars != 'string',
1047			indicators = [],
1048			indicator;
1049
1050		var that = this;
1051
1052		this.indicators = [];
1053
1054		if ( this.options.scrollbars ) {
1055			// Vertical scrollbar
1056			if ( this.options.scrollY ) {
1057				indicator = {
1058					el: createDefaultScrollbar('v', interactive, this.options.scrollbars),
1059					interactive: interactive,
1060					defaultScrollbars: true,
1061					customStyle: customStyle,
1062					resize: this.options.resizeScrollbars,
1063					shrink: this.options.shrinkScrollbars,
1064					fade: this.options.fadeScrollbars,
1065					listenX: false
1066				};
1067
1068				this.wrapper.appendChild(indicator.el);
1069				indicators.push(indicator);
1070			}
1071
1072			// Horizontal scrollbar
1073			if ( this.options.scrollX ) {
1074				indicator = {
1075					el: createDefaultScrollbar('h', interactive, this.options.scrollbars),
1076					interactive: interactive,
1077					defaultScrollbars: true,
1078					customStyle: customStyle,
1079					resize: this.options.resizeScrollbars,
1080					shrink: this.options.shrinkScrollbars,
1081					fade: this.options.fadeScrollbars,
1082					listenY: false
1083				};
1084
1085				this.wrapper.appendChild(indicator.el);
1086				indicators.push(indicator);
1087			}
1088		}
1089
1090		if ( this.options.indicators ) {
1091			// TODO: check concat compatibility
1092			indicators = indicators.concat(this.options.indicators);
1093		}
1094
1095		for ( var i = indicators.length; i--; ) {
1096			this.indicators.push( new Indicator(this, indicators[i]) );
1097		}
1098
1099		// TODO: check if we can use array.map (wide compatibility and performance issues)
1100		function _indicatorsMap (fn) {
1101			if (that.indicators) {
1102				for ( var i = that.indicators.length; i--; ) {
1103					fn.call(that.indicators[i]);
1104				}
1105			}
1106		}
1107
1108		if ( this.options.fadeScrollbars ) {
1109			this.on('scrollEnd', function () {
1110				_indicatorsMap(function () {
1111					this.fade();
1112				});
1113			});
1114
1115			this.on('scrollCancel', function () {
1116				_indicatorsMap(function () {
1117					this.fade();
1118				});
1119			});
1120
1121			this.on('scrollStart', function () {
1122				_indicatorsMap(function () {
1123					this.fade(1);
1124				});
1125			});
1126
1127			this.on('beforeScrollStart', function () {
1128				_indicatorsMap(function () {
1129					this.fade(1, true);
1130				});
1131			});
1132		}
1133
1134
1135		this.on('refresh', function () {
1136			_indicatorsMap(function () {
1137				this.refresh();
1138			});
1139		});
1140
1141		this.on('destroy', function () {
1142			_indicatorsMap(function () {
1143				this.destroy();
1144			});
1145
1146			delete this.indicators;
1147		});
1148	},
1149
1150	_initWheel: function () {
1151		utils.addEvent(this.wrapper, 'wheel', this);
1152		utils.addEvent(this.wrapper, 'mousewheel', this);
1153		utils.addEvent(this.wrapper, 'DOMMouseScroll', this);
1154
1155		this.on('destroy', function () {
1156			clearTimeout(this.wheelTimeout);
1157			this.wheelTimeout = null;
1158			utils.removeEvent(this.wrapper, 'wheel', this);
1159			utils.removeEvent(this.wrapper, 'mousewheel', this);
1160			utils.removeEvent(this.wrapper, 'DOMMouseScroll', this);
1161		});
1162	},
1163
1164	_wheel: function (e) {
1165		if ( !this.enabled ) {
1166			return;
1167		}
1168
1169		e.preventDefault();
1170
1171		var wheelDeltaX, wheelDeltaY,
1172			newX, newY,
1173			that = this;
1174
1175		if ( this.wheelTimeout === undefined ) {
1176			that._execEvent('scrollStart');
1177		}
1178
1179		// Execute the scrollEnd event after 400ms the wheel stopped scrolling
1180		clearTimeout(this.wheelTimeout);
1181		this.wheelTimeout = setTimeout(function () {
1182			if(!that.options.snap) {
1183				that._execEvent('scrollEnd');
1184			}
1185			that.wheelTimeout = undefined;
1186		}, 400);
1187
1188		if ( 'deltaX' in e ) {
1189			if (e.deltaMode === 1) {
1190				wheelDeltaX = -e.deltaX * this.options.mouseWheelSpeed;
1191				wheelDeltaY = -e.deltaY * this.options.mouseWheelSpeed;
1192			} else {
1193				wheelDeltaX = -e.deltaX;
1194				wheelDeltaY = -e.deltaY;
1195			}
1196		} else if ( 'wheelDeltaX' in e ) {
1197			wheelDeltaX = e.wheelDeltaX / 120 * this.options.mouseWheelSpeed;
1198			wheelDeltaY = e.wheelDeltaY / 120 * this.options.mouseWheelSpeed;
1199		} else if ( 'wheelDelta' in e ) {
1200			wheelDeltaX = wheelDeltaY = e.wheelDelta / 120 * this.options.mouseWheelSpeed;
1201		} else if ( 'detail' in e ) {
1202			wheelDeltaX = wheelDeltaY = -e.detail / 3 * this.options.mouseWheelSpeed;
1203		} else {
1204			return;
1205		}
1206
1207		wheelDeltaX *= this.options.invertWheelDirection;
1208		wheelDeltaY *= this.options.invertWheelDirection;
1209
1210		if ( !this.hasVerticalScroll ) {
1211			wheelDeltaX = wheelDeltaY;
1212			wheelDeltaY = 0;
1213		}
1214
1215		if ( this.options.snap ) {
1216			newX = this.currentPage.pageX;
1217			newY = this.currentPage.pageY;
1218
1219			if ( wheelDeltaX > 0 ) {
1220				newX--;
1221			} else if ( wheelDeltaX < 0 ) {
1222				newX++;
1223			}
1224
1225			if ( wheelDeltaY > 0 ) {
1226				newY--;
1227			} else if ( wheelDeltaY < 0 ) {
1228				newY++;
1229			}
1230
1231			this.goToPage(newX, newY);
1232
1233			return;
1234		}
1235
1236		newX = this.x + Math.round(this.hasHorizontalScroll ? wheelDeltaX : 0);
1237		newY = this.y + Math.round(this.hasVerticalScroll ? wheelDeltaY : 0);
1238
1239		this.directionX = wheelDeltaX > 0 ? -1 : wheelDeltaX < 0 ? 1 : 0;
1240		this.directionY = wheelDeltaY > 0 ? -1 : wheelDeltaY < 0 ? 1 : 0;
1241
1242		if ( newX > 0 ) {
1243			newX = 0;
1244		} else if ( newX < this.maxScrollX ) {
1245			newX = this.maxScrollX;
1246		}
1247
1248		if ( newY > 0 ) {
1249			newY = 0;
1250		} else if ( newY < this.maxScrollY ) {
1251			newY = this.maxScrollY;
1252		}
1253
1254		this.scrollTo(newX, newY, 0);
1255
1256		if ( this.options.probeType > 1 ) {
1257			this._execEvent('scroll');
1258		}
1259
1260// INSERT POINT: _wheel
1261	},
1262
1263	_initSnap: function () {
1264		this.currentPage = {};
1265
1266		if ( typeof this.options.snap == 'string' ) {
1267			this.options.snap = this.scroller.querySelectorAll(this.options.snap);
1268		}
1269
1270		this.on('refresh', function () {
1271			var i = 0, l,
1272				m = 0, n,
1273				cx, cy,
1274				x = 0, y,
1275				stepX = this.options.snapStepX || this.wrapperWidth,
1276				stepY = this.options.snapStepY || this.wrapperHeight,
1277				el,
1278				rect;
1279
1280			this.pages = [];
1281
1282			if ( !this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight ) {
1283				return;
1284			}
1285
1286			if ( this.options.snap === true ) {
1287				cx = Math.round( stepX / 2 );
1288				cy = Math.round( stepY / 2 );
1289
1290				while ( x > -this.scrollerWidth ) {
1291					this.pages[i] = [];
1292					l = 0;
1293					y = 0;
1294
1295					while ( y > -this.scrollerHeight ) {
1296						this.pages[i][l] = {
1297							x: Math.max(x, this.maxScrollX),
1298							y: Math.max(y, this.maxScrollY),
1299							width: stepX,
1300							height: stepY,
1301							cx: x - cx,
1302							cy: y - cy
1303						};
1304
1305						y -= stepY;
1306						l++;
1307					}
1308
1309					x -= stepX;
1310					i++;
1311				}
1312			} else {
1313				el = this.options.snap;
1314				l = el.length;
1315				n = -1;
1316
1317				for ( ; i < l; i++ ) {
1318					rect = utils.getRect(el[i]);
1319					if ( i === 0 || rect.left <= utils.getRect(el[i-1]).left ) {
1320						m = 0;
1321						n++;
1322					}
1323
1324					if ( !this.pages[m] ) {
1325						this.pages[m] = [];
1326					}
1327
1328					x = Math.max(-rect.left, this.maxScrollX);
1329					y = Math.max(-rect.top, this.maxScrollY);
1330					cx = x - Math.round(rect.width / 2);
1331					cy = y - Math.round(rect.height / 2);
1332
1333					this.pages[m][n] = {
1334						x: x,
1335						y: y,
1336						width: rect.width,
1337						height: rect.height,
1338						cx: cx,
1339						cy: cy
1340					};
1341
1342					if ( x > this.maxScrollX ) {
1343						m++;
1344					}
1345				}
1346			}
1347
1348			this.goToPage(this.currentPage.pageX || 0, this.currentPage.pageY || 0, 0);
1349
1350			// Update snap threshold if needed
1351			if ( this.options.snapThreshold % 1 === 0 ) {
1352				this.snapThresholdX = this.options.snapThreshold;
1353				this.snapThresholdY = this.options.snapThreshold;
1354			} else {
1355				this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * this.options.snapThreshold);
1356				this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * this.options.snapThreshold);
1357			}
1358		});
1359
1360		this.on('flick', function () {
1361			var time = this.options.snapSpeed || Math.max(
1362					Math.max(
1363						Math.min(Math.abs(this.x - this.startX), 1000),
1364						Math.min(Math.abs(this.y - this.startY), 1000)
1365					), 300);
1366
1367			this.goToPage(
1368				this.currentPage.pageX + this.directionX,
1369				this.currentPage.pageY + this.directionY,
1370				time
1371			);
1372		});
1373	},
1374
1375	_nearestSnap: function (x, y) {
1376		if ( !this.pages.length ) {
1377			return { x: 0, y: 0, pageX: 0, pageY: 0 };
1378		}
1379
1380		var i = 0,
1381			l = this.pages.length,
1382			m = 0;
1383
1384		// Check if we exceeded the snap threshold
1385		if ( Math.abs(x - this.absStartX) < this.snapThresholdX &&
1386			Math.abs(y - this.absStartY) < this.snapThresholdY ) {
1387			return this.currentPage;
1388		}
1389
1390		if ( x > 0 ) {
1391			x = 0;
1392		} else if ( x < this.maxScrollX ) {
1393			x = this.maxScrollX;
1394		}
1395
1396		if ( y > 0 ) {
1397			y = 0;
1398		} else if ( y < this.maxScrollY ) {
1399			y = this.maxScrollY;
1400		}
1401
1402		for ( ; i < l; i++ ) {
1403			if ( x >= this.pages[i][0].cx ) {
1404				x = this.pages[i][0].x;
1405				break;
1406			}
1407		}
1408
1409		l = this.pages[i].length;
1410
1411		for ( ; m < l; m++ ) {
1412			if ( y >= this.pages[0][m].cy ) {
1413				y = this.pages[0][m].y;
1414				break;
1415			}
1416		}
1417
1418		if ( i == this.currentPage.pageX ) {
1419			i += this.directionX;
1420
1421			if ( i < 0 ) {
1422				i = 0;
1423			} else if ( i >= this.pages.length ) {
1424				i = this.pages.length - 1;
1425			}
1426
1427			x = this.pages[i][0].x;
1428		}
1429
1430		if ( m == this.currentPage.pageY ) {
1431			m += this.directionY;
1432
1433			if ( m < 0 ) {
1434				m = 0;
1435			} else if ( m >= this.pages[0].length ) {
1436				m = this.pages[0].length - 1;
1437			}
1438
1439			y = this.pages[0][m].y;
1440		}
1441
1442		return {
1443			x: x,
1444			y: y,
1445			pageX: i,
1446			pageY: m
1447		};
1448	},
1449
1450	goToPage: function (x, y, time, easing) {
1451		easing = easing || this.options.bounceEasing;
1452
1453		if ( x >= this.pages.length ) {
1454			x = this.pages.length - 1;
1455		} else if ( x < 0 ) {
1456			x = 0;
1457		}
1458
1459		if ( y >= this.pages[x].length ) {
1460			y = this.pages[x].length - 1;
1461		} else if ( y < 0 ) {
1462			y = 0;
1463		}
1464
1465		var posX = this.pages[x][y].x,
1466			posY = this.pages[x][y].y;
1467
1468		time = time === undefined ? this.options.snapSpeed || Math.max(
1469			Math.max(
1470				Math.min(Math.abs(posX - this.x), 1000),
1471				Math.min(Math.abs(posY - this.y), 1000)
1472			), 300) : time;
1473
1474		this.currentPage = {
1475			x: posX,
1476			y: posY,
1477			pageX: x,
1478			pageY: y
1479		};
1480
1481		this.scrollTo(posX, posY, time, easing);
1482	},
1483
1484	next: function (time, easing) {
1485		var x = this.currentPage.pageX,
1486			y = this.currentPage.pageY;
1487
1488		x++;
1489
1490		if ( x >= this.pages.length && this.hasVerticalScroll ) {
1491			x = 0;
1492			y++;
1493		}
1494
1495		this.goToPage(x, y, time, easing);
1496	},
1497
1498	prev: function (time, easing) {
1499		var x = this.currentPage.pageX,
1500			y = this.currentPage.pageY;
1501
1502		x--;
1503
1504		if ( x < 0 && this.hasVerticalScroll ) {
1505			x = 0;
1506			y--;
1507		}
1508
1509		this.goToPage(x, y, time, easing);
1510	},
1511
1512	_initKeys: function (e) {
1513		// default key bindings
1514		var keys = {
1515			pageUp: 33,
1516			pageDown: 34,
1517			end: 35,
1518			home: 36,
1519			left: 37,
1520			up: 38,
1521			right: 39,
1522			down: 40
1523		};
1524		var i;
1525
1526		// if you give me characters I give you keycode
1527		if ( typeof this.options.keyBindings == 'object' ) {
1528			for ( i in this.options.keyBindings ) {
1529				if ( typeof this.options.keyBindings[i] == 'string' ) {
1530					this.options.keyBindings[i] = this.options.keyBindings[i].toUpperCase().charCo
1530deAt(0);
1531				}
1532			}
1533		} else {
1534			this.options.keyBindings = {};
1535		}
1536
1537		for ( i in keys ) {
1538			this.options.keyBindings[i] = this.options.keyBindings[i] || keys[i];
1539		}
1540
1541		utils.addEvent(window, 'keydown', this);
1542
1543		this.on('destroy', function () {
1544			utils.removeEvent(window, 'keydown', this);
1545		});
1546	},
1547
1548	_key: function (e) {
1549		if ( !this.enabled ) {
1550			return;
1551		}
1552
1553		var snap = this.options.snap,	// we are using this alot, better to cache it
1554			newX = snap ? this.currentPage.pageX : this.x,
1555			newY = snap ? this.currentPage.pageY : this.y,
1556			now = utils.getTime(),
1557			prevTime = this.keyTime || 0,
1558			acceleration = 0.250,
1559			pos;
1560
1561		if ( this.options.useTransition && this.isInTransition ) {
1562			pos = this.getComputedPosition();
1563
1564			this._translate(Math.round(pos.x), Math.round(pos.y));
1565			this.isInTransition = false;
1566		}
1567
1568		this.keyAcceleration = now - prevTime < 200 ? Math.min(this.keyAcceleration + acceleration, 50) : 0;
1569
1570		switch ( e.keyCode ) {
1571			case this.options.keyBindings.pageUp:
1572				if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) {
1573					newX += snap ? 1 : this.wrapperWidth;
1574				} else {
1575					newY += snap ? 1 : this.wrapperHeight;
1576				}
1577				break;
1578			case this.options.keyBindings.pageDown:
1579				if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) {
1580					newX -= snap ? 1 : this.wrapperWidth;
1581				} else {
1582					newY -= snap ? 1 : this.wrapperHeight;
1583				}
1584				break;
1585			case this.options.keyBindings.end:
1586				newX = snap ? this.pages.length-1 : this.maxScrollX;
1587				newY = snap ? this.pages[0].length-1 : this.maxScrollY;
1588				break;
1589			case this.options.keyBindings.home:
1590				newX = 0;
1591				newY = 0;
1592				break;
1593			case this.options.keyBindings.left:
1594				newX += snap ? -1 : 5 + this.keyAcceleration>>0;
1595				break;
1596			case this.options.keyBindings.up:
1597				newY += snap ? 1 : 5 + this.keyAcceleration>>0;
1598				break;
1599			case this.options.keyBindings.right:
1600				newX -= snap ? -1 : 5 + this.keyAcceleration>>0;
1601				break;
1602			case this.options.keyBindings.down:
1603				newY -= snap ? 1 : 5 + this.keyAcceleration>>0;
1604				break;
1605			default:
1606				return;
1607		}
1608
1609		if ( snap ) {
1610			this.goToPage(newX, newY);
1611			return;
1612		}
1613
1614		if ( newX > 0 ) {
1615			newX = 0;
1616			this.keyAcceleration = 0;
1617		} else if ( newX < this.maxScrollX ) {
1618			newX = this.maxScrollX;
1619			this.keyAcceleration = 0;
1620		}
1621
1622		if ( newY > 0 ) {
1623			newY = 0;
1624			this.keyAcceleration = 0;
1625		} else if ( newY < this.maxScrollY ) {
1626			newY = this.maxScrollY;
1627			this.keyAcceleration = 0;
1628		}
1629
1630		this.scrollTo(newX, newY, 0);
1631
1632		this.keyTime = now;
1633	},
1634
1635	_animate: function (destX, destY, duration, easingFn) {
1636		var that = this,
1637			startX = this.x,
1638			startY = this.y,
1639			startTime = utils.getTime(),
1640			destTime = startTime + duration;
1641
1642		function step () {
1643			var now = utils.getTime(),
1644				newX, newY,
1645				easing;
1646
1647			if ( now >= destTime ) {
1648				that.isAnimating = false;
1649				that._translate(destX, destY);
1650				
1651				if ( !that.resetPosition(that.options.bounceTime) ) {
1652					that._execEvent('scrollEnd');
1653				}
1654
1655				return;
1656			}
1657
1658			now = ( now - startTime ) / duration;
1659			easing = easingFn(now);
1660			newX = ( destX - startX ) * easing + startX;
1661			newY = ( destY - startY ) * easing + startY;
1662			that._translate(newX, newY);
1663
1664			if ( that.isAnimating ) {
1665				rAF(step);
1666			}
1667
1668			if ( that.options.probeType == 3 ) {
1669				that._execEvent('scroll');
1670			}
1671		}
1672
1673		this.isAnimating = true;
1674		step();
1675	},
1676
1677	handleEvent: function (e) {
1678		switch ( e.type ) {
1679			case 'touchstart':
1680			case 'pointerdown':
1681			case 'MSPointerDown':
1682			case 'mousedown':
1683				this._start(e);
1684				break;
1685			case 'touchmove':
1686			case 'pointermove':
1687			case 'MSPointerMove':
1688			case 'mousemove':
1689				this._move(e);
1690				break;
1691			case 'touchend':
1692			case 'pointerup':
1693			case 'MSPointerUp':
1694			case 'mouseup':
1695			case 'touchcancel':
1696			case 'pointercancel':
1697			case 'MSPointerCancel':
1698			case 'mousecancel':
1699				this._end(e);
1700				break;
1701			case 'orientationchange':
1702			case 'resize':
1703				this._resize();
1704				break;
1705			case 'transitionend':
1706			case 'webkitTransitionEnd':
1707			case 'oTransitionEnd':
1708			case 'MSTransitionEnd':
1709				this._transitionEnd(e);
1710				break;
1711			case 'wheel':
1712			case 'DOMMouseScroll':
1713			case 'mousewheel':
1714				this._wheel(e);
1715				break;
1716			case 'keydown':
1717				this._key(e);
1718				break;
1719			case 'click':
1720				if ( this.enabled && !e._constructed ) {
1721					e.preventDefault();
1722					e.stopPropagation();
1723				}
1724				break;
1725		}
1726	}
1727};
1728function createDefaultScrollbar (direction, interactive, type) {
1729	var scrollbar = document.createElement('div'),
1730		indicator = document.createElement('div');
1731
1732	if ( type === true ) {
1733		scrollbar.style.cssText = 'position:absolute;z-index:9999';
1734		indicator.style.cssText = '-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px';
1735	}
1736
1737	indicator.className = 'iScrollIndicator';
1738
1739	if ( direction == 'h' ) {
1740		if ( type === true ) {
1741			scrollbar.style.cssText += ';height:7px;left:2px;right:2px;bottom:0';
1742			indicator.style.height = '100%';
1743		}
1744		scrollbar.className = 'iScrollHorizontalScrollbar';
1745	} else {
1746		if ( type === true ) {
1747			scrollbar.style.cssText += ';width:7px;bottom:2px;top:2px;right:1px';
1748			indicator.style.width = '100%';
1749		}
1750		scrollbar.className = 'iScrollVerticalScrollbar';
1751	}
1752
1753	scrollbar.style.cssText += ';overflow:hidden';
1754
1755	if ( !interactive ) {
1756		scrollbar.style.pointerEvents = 'none';
1757	}
1758
1759	scrollbar.appendChild(indicator);
1760
1761	return scrollbar;
1762}
1763
1764function Indicator (scroller, options) {
1765	this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el;
1766	this.wrapperStyle = this.wrapper.style;
1767	this.indicator = this.wrapper.children[0];
1768	this.indicatorStyle = this.indicator.style;
1769	this.scroller = scroller;
1770
1771	this.options = {
1772		listenX: true,
1773		listenY: true,
1774		interactive: false,
1775		resize: true,
1776		defaultScrollbars: false,
1777		shrink: false,
1778		fade: false,
1779		speedRatioX: 0,
1780		speedRatioY: 0
1781	};
1782
1783	for ( var i in options ) {
1784		this.options[i] = options[i];
1785	}
1786
1787	this.sizeRatioX = 1;
1788	this.sizeRatioY = 1;
1789	this.maxPosX = 0;
1790	this.maxPosY = 0;
1791
1792	if ( this.options.interactive ) {
1793		if ( !this.options.disableTouch ) {
1794			utils.addEvent(this.indicator, 'touchstart', this);
1795			utils.addEvent(window, 'touchend', this);
1796		}
1797		if ( !this.options.disablePointer ) {
1798			utils.addEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this);
1799			utils.addEvent(window, utils.prefixPointerEvent('pointerup'), this);
1800		}
1801		if ( !this.options.disableMouse ) {
1802			utils.addEvent(this.indicator, 'mousedown', this);
1803			utils.addEvent(window, 'mouseup', this);
1804		}
1805	}
1806
1807	if ( this.options.fade ) {
1808		this.wrapperStyle[utils.style.transform] = this.scroller.translateZ;
1809		var durationProp = utils.style.transitionDuration;
1810		if(!durationProp) {
1811			return;
1812		}
1813		this.wrapperStyle[durationProp] = utils.isBadAndroid ? '0.0001ms' : '0ms';
1814		// remove 0.0001ms
1815		var self = this;
1816		if(utils.isBadAndroid) {
1817			rAF(function() {
1818				if(self.wrapperStyle[durationProp] === '0.0001ms') {
1819					self.wrapperStyle[durationProp] = '0s';
1820				}
1821			});
1822		}
1823		this.wrapperStyle.opacity = '0';
1824	}
1825}
1826
1827Indicator.prototype = {
1828	handleEvent: function (e) {
1829		switch ( e.type ) {
1830			case 'touchstart':
1831			case 'pointerdown':
1832			case 'MSPointerDown':
1833			case 'mousedown':
1834				this._start(e);
1835				break;
1836			case 'touchmove':
1837			case 'pointermove':
1838			case 'MSPointerMove':
1839			case 'mousemove':
1840				this._move(e);
1841				break;
1842			case 'touchend':
1843			case 'pointerup':
1844			case 'MSPointerUp':
1845			case 'mouseup':
1846			case 'touchcancel':
1847			case 'pointercancel':
1848			case 'MSPointerCancel':
1849			case 'mousecancel':
1850				this._end(e);
1851				break;
1852		}
1853	},
1854
1855	destroy: function () {
1856		if ( this.options.fadeScrollbars ) {
1857			clearTimeout(this.fadeTimeout);
1858			this.fadeTimeout = null;
1859		}
1860		if ( this.options.interactive ) {
1861			utils.removeEvent(this.indicator, 'touchstart', this);
1862			utils.removeEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this);
1863			utils.removeEvent(this.indicator, 'mousedown', this);
1864
1865			utils.removeEvent(window, 'touchmove', this);
1866			utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this);
1867			utils.removeEvent(window, 'mousemove', this);
1868
1869			utils.removeEvent(window, 'touchend', this);
1870			utils.removeEvent(window, utils.prefixPointerEvent('pointerup'), this);
1871			utils.removeEvent(window, 'mouseup', this);
1872		}
1873
1874		if ( this.options.defaultScrollbars && this.wrapper.parentNode ) {
1875			this.wrapper.parentNode.removeChild(this.wrapper);
1876		}
1877	},
1878
1879	_start: function (e) {
1880		var point = e.touches ? e.touches[0] : e;
1881
1882		e.preventDefault();
1883		e.stopPropagation();
1884
1885		this.transitionTime();
1886
1887		this.initiated = true;
1888		this.moved = false;
1889		this.lastPointX	= point.pageX;
1890		this.lastPointY	= point.pageY;
1891
1892		this.startTime	= utils.getTime();
1893
1894		if ( !this.options.disableTouch ) {
1895			utils.addEvent(window, 'touchmove', this);
1896		}
1897		if ( !this.options.disablePointer ) {
1898			utils.addEvent(window, utils.prefixPointerEvent('pointermove'), this);
1899		}
1900		if ( !this.options.disableMouse ) {
1901			utils.addEvent(window, 'mousemove', this);
1902		}
1903
1904		this.scroller._execEvent('beforeScrollStart');
1905	},
1906
1907	_move: function (e) {
1908		var point = e.touches ? e.touches[0] : e,
1909			deltaX, deltaY,
1910			newX, newY,
1911			timestamp = utils.getTime();
1912
1913		if ( !this.moved ) {
1914			this.scroller._execEvent('scrollStart');
1915		}
1916
1917		this.moved = true;
1918
1919		deltaX = point.pageX - this.lastPointX;
1920		this.lastPointX = point.pageX;
1921
1922		deltaY = point.pageY - this.lastPointY;
1923		this.lastPointY = point.pageY;
1924
1925		newX = this.x + deltaX;
1926		newY = this.y + deltaY;
1927
1928		this._pos(newX, newY);
1929
1930
1931		if ( this.scroller.options.probeType == 1 && timestamp - this.startTime > 300 ) {
1932			this.startTime = timestamp;
1933			this.scroller._execEvent('scroll');
1934		} else if ( this.scroller.options.probeType > 1 ) {
1935			this.scroller._execEvent('scroll');
1936		}
1937
1938
1939// INSERT POINT: indicator._move
1940
1941		e.preventDefault();
1942		e.stopPropagation();
1943	},
1944
1945	_end: function (e) {
1946		if ( !this.initiated ) {
1947			return;
1948		}
1949
1950		this.initiated = false;
1951
1952		e.preventDefault();
1953		e.stopPropagation();
1954
1955		utils.removeEvent(window, 'touchmove', this);
1956		utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this);
1957		utils.removeEvent(window, 'mousemove', this);
1958
1959		if ( this.scroller.options.snap ) {
1960			var snap = this.scroller._nearestSnap(this.scroller.x, this.scroller.y);
1961
1962			var time = this.options.snapSpeed || Math.max(
1963					Math.max(
1964						Math.min(Math.abs(this.scroller.x - snap.x), 1000),
1965						Math.min(Math.abs(this.scroller.y - snap.y), 1000)
1966					), 300);
1967
1968			if ( this.scroller.x != snap.x || this.scroller.y != snap.y ) {
1969				this.scroller.directionX = 0;
1970				this.scroller.directionY = 0;
1971				this.scroller.currentPage = snap;
1972				this.scroller.scrollTo(snap.x, snap.y, time, this.scroller.options.bounceEasing);
1973			}
1974		}
1975
1976		if ( this.moved ) {
1977			this.scroller._execEvent('scrollEnd');
1978		}
1979	},
1980
1981	transitionTime: function (time) {
1982		time = time || 0;
1983		var durationProp = utils.style.transitionDuration;
1984		if(!durationProp) {
1985			return;
1986		}
1987
1988		this.indicatorStyle[durationProp] = time + 'ms';
1989
1990		if ( !time && utils.isBadAndroid ) {
1991			this.indicatorStyle[durationProp] = '0.0001ms';
1992			// remove 0.0001ms
1993			var self = this;
1994			rAF(function() {
1995				if(self.indicatorStyle[durationProp] === '0.0001ms') {
1996					self.indicatorStyle[durationProp] = '0s';
1997				}
1998			});
1999		}
2000	},
2001
2002	transitionTimingFunction: function (easing) {
2003		this.indicatorStyle[utils.style.transitionTimingFunction] = easing;
2004	},
2005
2006	refresh: function () {
2007		this.transitionTime();
2008
2009		if ( this.options.listenX && !this.options.listenY ) {
2010			this.indicatorStyle.display = this.scroller.hasHorizontalScroll ? 'block' : 'none';
2011		} else if ( this.options.listenY && !this.options.listenX ) {
2012			this.indicatorStyle.display = this.scroller.hasVerticalScroll ? 'block' : 'none';
2013		} else {
2014			this.indicatorStyle.display = this.scroller.hasHorizontalScroll || this.scroller.hasVerticalScroll ? 'block' : 'none';
2015		}
2016
2017		if ( this.scroller.hasHorizontalScroll && this.scroller.hasVerticalScroll ) {
2018			utils.addClass(this.wrapper, 'iScrollBothScrollbars');
2019			utils.removeClass(this.wrapper, 'iScrollLoneScrollbar');
2020
2021			if ( this.options.defaultScrollbars && this.options.customStyle ) {
2022				if ( this.options.listenX ) {
2023					this.wrapper.style.right = '8px';
2024				} else {
2025					this.wrapper.style.bottom = '8px';
2026				}
2027			}
2028		} else {
2029			utils.removeClass(this.wrapper, 'iScrollBothScrollbars');
2030			utils.addClass(this.wrapper, 'iScrollLoneScrollbar');
2031
2032			if ( this.options.defaultScrollbars && this.options.customStyle ) {
2033				if ( this.options.listenX ) {
2034					this.wrapper.style.right = '2px';
2035				} else {
2036					this.wrapper.style.bottom = '2px';
2037				}
2038			}
2039		}
2040
2041		utils.getRect(this.wrapper);	// force refresh
2042
2043		if ( this.options.listenX ) {
2044			this.wrapperWidth = this.wrapper.clientWidth;
2045			if ( this.options.resize ) {
2046				this.indicatorWidth = Math.max(Math.round(this.wrapperWidth * this.wrapperWidth / (this.scroller.scrollerWidth || this.wrapperWidth || 1)), 8);
2047				this.indicatorStyle.width = this.indicatorWidth + 'px';
2048			} else {
2049				this.indicatorWidth = this.indicator.clientWidth;
2050			}
2051
2052			this.maxPosX = this.wrapperWidth - this.indicatorWidth;
2053
2054			if ( this.options.shrink == 'clip' ) {
2055				this.minBoundaryX = -this.indicatorWidth + 8;
2056				this.maxBoundaryX = this.wrapperWidth - 8;
2057			} else {
2058				this.minBoundaryX = 0;
2059				this.maxBoundaryX = this.maxPosX;
2060			}
2061
2062			this.sizeRatioX = this.options.speedRatioX || (this.scroller.maxScrollX && (this.maxPosX / this.scroller.maxScrollX));
2063		}
2064
2065		if ( this.options.listenY ) {
2066			this.wrapperHeight = this.wrapper.clientHeight;
2067			if ( this.options.resize ) {
2068				this.indicatorHeight = Math.max(Math.round(this.wrapperHeight * this.wrapperHeight / (this.scroller.scrollerHeight || this.wrapperHeight || 1)), 8);
2069				this.indicatorStyle.height = this.indicatorHeight + 'px';
2070			} else {
2071				this.indicatorHeight = this.indicator.clientHeight;
2072			}
2073
2074			this.maxPosY = this.wrapperHeight - this.indicatorHeight;
2075
2076			if ( this.options.shrink == 'clip' ) {
2077				this.minBoundaryY = -this.indicatorHeight + 8;
2078				this.maxBoundaryY = this.wrapperHeight - 8;
2079			} else {
2080				this.minBoundaryY = 0;
2081				this.maxBoundaryY = this.maxPosY;
2082			}
2083
2084			this.maxPosY = this.wrapperHeight - this.indicatorHeight;
2085			this.sizeRatioY = this.options.speedRatioY || (this.scroller.maxScrollY && (this.maxPosY / this.scroller.maxScrollY));
2086		}
2087
2088		this.updatePosition();
2089	},
2090
2091	updatePosition: function () {
2092		var x = this.options.listenX && Math.round(this.sizeRatioX * this.scroller.x) || 0,
2093			y = this.options.listenY && Math.round(this.sizeRatioY * this.scroller.y) || 0;
2094
2095		if ( !this.options.ignoreBoundaries ) {
2096			if ( x < this.minBoundaryX ) {
2097				if ( this.options.shrink == 'scale' ) {
2098					this.width = Math.max(this.indicatorWidth + x, 8);
2099					this.indicatorStyle.width = this.width + 'px';
2100				}
2101				x = this.minBoundaryX;
2102			} else if ( x > this.maxBoundaryX ) {
2103				if ( this.options.shrink == 'scale' ) {
2104					this.width = Math.max(this.indicatorWidth - (x - this.maxPosX), 8);
2105					this.indicatorStyle.width = this.width + 'px';
2106					x = this.maxPosX + this.indicatorWidth - this.width;
2107				} else {
2108					x = this.maxBoundaryX;
2109				}
2110			} else if ( this.options.shrink == 'scale' && this.width != this.indicatorWidth ) {
2111				this.width = this.indicatorWidth;
2112				this.indicatorStyle.width = this.width + 'px';
2113			}
2114
2115			if ( y < this.minBoundaryY ) {
2116				if ( this.options.shrink == 'scale' ) {
2117					this.height = Math.max(this.indicatorHeight + y * 3, 8);
2118					this.indicatorStyle.height = this.height + 'px';
2119				}
2120				y = this.minBoundaryY;
2121			} else if ( y > this.maxBoundaryY ) {
2122				if ( this.options.shrink == 'scale' ) {
2123					this.height = Math.max(this.indicatorHeight - (y - this.maxPosY) * 3, 8);
2124					this.indicatorStyle.height = this.height + 'px';
2125					y = this.maxPosY + this.indicatorHeight - this.height;
2126				} else {
2127					y = this.maxBoundaryY;
2128				}
2129			} else if ( this.options.shrink == 'scale' && this.height != this.indicatorHeight ) {
2130				this.height = this.indicatorHeight;
2131				this.indicatorStyle.height = this.height + 'px';
2132			}
2133		}
2134
2135		this.x = x;
2136		this.y = y;
2137
2138		if ( this.scroller.options.useTransform ) {
2139			this.indicatorStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.scroller.translateZ;
2140		} else {
2141			this.indicatorStyle.left = x + 'px';
2142			this.indicatorStyle.top = y + 'px';
2143		}
2144	},
2145
2146	_pos: function (x, y) {
2147		if ( x < 0 ) {
2148			x = 0;
2149		} else if ( x > this.maxPosX ) {
2150			x = this.maxPosX;
2151		}
2152
2153		if ( y < 0 ) {
2154			y = 0;
2155		} else if ( y > this.maxPosY ) {
2156			y = this.maxPosY;
2157		}
2158
2159		x = this.options.listenX ? Math.round(x / this.sizeRatioX) : this.scroller.x;
2160		y = this.options.listenY ? Math.round(y / this.sizeRatioY) : this.scroller.y;
2161
2162		this.scroller.scrollTo(x, y);
2163	},
2164
2165	fade: function (val, hold) {
2166		if ( hold && !this.visible ) {
2167			return;
2168		}
2169
2170		clearTimeout(this.fadeTimeout);
2171		this.fadeTimeout = null;
2172
2173		var time = val ? 250 : 500,
2174			delay = val ? 0 : 300;
2175
2176		val = val ? '1' : '0';
2177
2178		this.wrapperStyle[utils.style.transitionDuration] = time + 'ms';
2179
2180		this.fadeTimeout = setTimeout((function (val) {
2181			this.wrapperStyle.opacity = val;
2182			this.visible = +val;
2183		}).bind(this, val), delay);
2184	}
2185};
2186
2187IScroll.utils = utils;
2188
2189if ( typeof module != 'undefined' && module.exports ) {
2190	module.exports = IScroll;
2191} else if ( typeof define == 'function' && define.amd ) {
2192        define( function () { return IScroll; } );
2193} else {
2194	window.IScroll = IScroll;
2195}
2196
2197})(window, document, Math);

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.