PageRenderTime 60ms CodeModel.GetById 2ms app.highlight 50ms RepoModel.GetById 2ms app.codeStats 0ms

/KitJs/src/labs/iScroll/iscroll.js

https://github.com/wuxq/KitJs
JavaScript | 666 lines | 524 code | 98 blank | 44 comment | 169 complexity | c38a4facfd10d222394e65ebec2bd699 MD5 | raw file
  1/**
  2 * @ignore
  3 * Find more about the scrolling function at
  4 * http://cubiq.org/iscroll
  5 *
  6 * Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/
  7 * Released under MIT license
  8 * http://cubiq.org/dropbox/mit-license.txt
  9 *
 10 * Version 3.7.1 - Last updated: 2010.10.08
 11 *
 12 */
 13
 14(function() {
 15	function iScroll(el, options) {
 16		var that = this, i;
 17		that.element = typeof el == 'object' ? el : document.getElementById(el);
 18		that.wrapper = that.element.parentNode;
 19
 20		that.element.style.webkitTransitionProperty = '-webkit-transform';
 21		that.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
 22		that.element.style.webkitTransitionDuration = '0';
 23		that.element.style.webkitTransform = translateOpen + '0,0' + translateClose;
 24
 25		// Default options
 26		that.options = {
 27			bounce : has3d,
 28			momentum : has3d,
 29			checkDOMChanges : true,
 30			topOnDOMChanges : false,
 31			hScrollbar : has3d,
 32			vScrollbar : has3d,
 33			fadeScrollbar : isIthing || !isTouch,
 34			shrinkScrollbar : isIthing || !isTouch,
 35			desktopCompatibility : false,
 36			overflow : 'auto',
 37			snap : false,
 38			bounceLock : false,
 39			scrollbarColor : 'rgba(0,0,0,0.5)',
 40			onScrollEnd : function() {
 41			}
 42		};
 43
 44		// User defined options
 45		if( typeof options == 'object') {
 46			for(i in options) {
 47				that.options[i] = options[i];
 48			}
 49		}
 50
 51		if(that.options.desktopCompatibility) {
 52			that.options.overflow = 'hidden';
 53		}
 54
 55		that.onScrollEnd = that.options.onScrollEnd;
 56		delete that.options.onScrollEnd;
 57
 58		that.wrapper.style.overflow = that.options.overflow;
 59
 60		that.refresh();
 61
 62		window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);
 63
 64		if(isTouch || that.options.desktopCompatibility) {
 65			that.element.addEventListener(START_EVENT, that, false);
 66			that.element.addEventListener(MOVE_EVENT, that, false);
 67			that.element.addEventListener(END_EVENT, that, false);
 68		}
 69
 70		if(that.options.checkDOMChanges) {
 71			that.element.addEventListener('DOMSubtreeModified', that, false);
 72		}
 73	}
 74
 75
 76	iScroll.prototype = {
 77		x : 0,
 78		y : 0,
 79		enabled : true,
 80
 81		handleEvent : function(e) {
 82			var that = this;
 83
 84			switch (e.type) {
 85				case START_EVENT:
 86					that.touchStart(e);
 87					break;
 88				case MOVE_EVENT:
 89					that.touchMove(e);
 90					break;
 91				case END_EVENT:
 92					that.touchEnd(e);
 93					break;
 94				case 'webkitTransitionEnd':
 95					that.transitionEnd();
 96					break;
 97				case 'orientationchange':
 98				case 'resize':
 99					that.refresh();
100					break;
101				case 'DOMSubtreeModified':
102					that.onDOMModified(e);
103					break;
104			}
105		},
106		onDOMModified : function(e) {
107			var that = this;
108
109			// (Hopefully) execute onDOMModified only once
110			if(e.target.parentNode != that.element) {
111				return;
112			}
113
114			setTimeout(function() {
115				that.refresh();
116			}, 0);
117			if(that.options.topOnDOMChanges && (that.x != 0 || that.y != 0)) {
118				that.scrollTo(0, 0, '0');
119			}
120		},
121		refresh : function() {
122			var that = this, resetX = that.x, resetY = that.y, snap;
123
124			that.scrollWidth = that.wrapper.clientWidth;
125			that.scrollHeight = that.wrapper.clientHeight;
126			that.scrollerWidth = that.element.offsetWidth;
127			that.scrollerHeight = that.element.offsetHeight;
128			that.maxScrollX = that.scrollWidth - that.scrollerWidth;
129			that.maxScrollY = that.scrollHeight - that.scrollerHeight;
130			that.directionX = 0;
131			that.directionY = 0;
132
133			if(that.scrollX) {
134				if(that.maxScrollX >= 0) {
135					resetX = 0;
136				} else if(that.x < that.maxScrollX) {
137					resetX = that.maxScrollX;
138				}
139			}
140			if(that.scrollY) {
141				if(that.maxScrollY >= 0) {
142					resetY = 0;
143				} else if(that.y < that.maxScrollY) {
144					resetY = that.maxScrollY;
145				}
146			}
147
148			// Snap
149			if(that.options.snap) {
150				that.maxPageX = -Math.floor(that.maxScrollX / that.scrollWidth);
151				that.maxPageY = -Math.floor(that.maxScrollY / that.scrollHeight);
152				snap = that.snap(resetX, resetY);
153				resetX = snap.x;
154				resetY = snap.y;
155			}
156
157			if(resetX != that.x || resetY != that.y) {
158				that.setTransitionTime('0');
159				that.setPosition(resetX, resetY, true);
160			}
161
162			that.scrollX = that.scrollerWidth > that.scrollWidth;
163			that.scrollY = !that.options.bounceLock && !that.scrollX || that.scrollerHeight > that.scrollHeight;
164
165			// Update horizontal scrollbar
166			if(that.options.hScrollbar && that.scrollX) {
167				that.scrollBarX = that.scrollBarX || new scrollbar('horizontal', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar, that.options.scrollbarColor);
168				that.scrollBarX.init(that.scrollWidth, that.scrollerWidth);
169			} else if(that.scrollBarX) {
170				that.scrollBarX = that.scrollBarX.remove();
171			}
172
173			// Update vertical scrollbar
174			if(that.options.vScrollbar && that.scrollY && that.scrollerHeight > that.scrollHeight) {
175				that.scrollBarY = that.scrollBarY || new scrollbar('vertical', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar, that.options.scrollbarColor);
176				that.scrollBarY.init(that.scrollHeight, that.scrollerHeight);
177			} else if(that.scrollBarY) {
178				that.scrollBarY = that.scrollBarY.remove();
179			}
180		},
181		setPosition : function(x, y, hideScrollBars) {
182			var that = this;
183
184			that.x = x;
185			that.y = y;
186
187			that.element.style.webkitTransform = translateOpen + that.x + 'px,' + that.y + 'px' + translateClose;
188
189			// Move the scrollbars
190			if(!hideScrollBars) {
191				if(that.scrollBarX) {
192					that.scrollBarX.setPosition(that.x);
193				}
194				if(that.scrollBarY) {
195					that.scrollBarY.setPosition(that.y);
196				}
197			}
198		},
199		setTransitionTime : function(time) {
200			var that = this;
201			time = time || '0';
202			that.element.style.webkitTransitionDuration = time;
203
204			if(that.scrollBarX) {
205				that.scrollBarX.bar.style.webkitTransitionDuration = time;
206				that.scrollBarX.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
207			}
208			if(that.scrollBarY) {
209				that.scrollBarY.bar.style.webkitTransitionDuration = time;
210				that.scrollBarY.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
211			}
212		},
213		touchStart : function(e) {
214			var that = this, matrix;
215
216			if(!that.enabled) {
217				return;
218			}
219
220			e.preventDefault();
221			e.stopPropagation();
222
223			that.scrolling = true;
224			// This is probably not needed, but may be useful if iScroll is used in conjuction with other frameworks
225
226			that.moved = false;
227			that.distX = 0;
228			that.distY = 0;
229
230			that.setTransitionTime('0');
231
232			// Check if the scroller is really where it should be
233			if(that.options.momentum || that.options.snap) {
234				matrix = new WebKitCSSMatrix(window.getComputedStyle(that.element).webkitTransform);
235				if(matrix.e != that.x || matrix.f != that.y) {
236					document.removeEventListener('webkitTransitionEnd', that, false);
237					that.setPosition(matrix.e, matrix.f);
238					that.moved = true;
239				}
240			}
241
242			that.touchStartX = isTouch ? e.changedTouches[0].pageX : e.pageX;
243			that.scrollStartX = that.x;
244
245			that.touchStartY = isTouch ? e.changedTouches[0].pageY : e.pageY;
246			that.scrollStartY = that.y;
247
248			that.scrollStartTime = e.timeStamp;
249
250			that.directionX = 0;
251			that.directionY = 0;
252		},
253		touchMove : function(e) {
254			if(!this.scrolling) {
255				return;
256			}
257
258			var that = this, pageX = isTouch ? e.changedTouches[0].pageX : e.pageX, pageY = isTouch ? e.changedTouches[0].pageY : e.pageY, leftDelta = that.scrollX ? pageX - that.touchStartX : 0, topDelta = that.scrollY ? pageY - that.touchStartY : 0, newX = that.x + leftDelta, newY = that.y + topDelta;
259
260			//e.preventDefault();
261			e.stopPropagation();
262			// Stopping propagation just saves some cpu cycles (I presume)
263
264			that.touchStartX = pageX;
265			that.touchStartY = pageY;
266
267			// Slow down if outside of the boundaries
268			if(newX >= 0 || newX < that.maxScrollX) {
269				newX = that.options.bounce ? Math.round(that.x + leftDelta / 3) : (newX >= 0 || that.maxScrollX >= 0) ? 0 : that.maxScrollX;
270			}
271			if(newY >= 0 || newY < that.maxScrollY) {
272				newY = that.options.bounce ? Math.round(that.y + topDelta / 3) : (newY >= 0 || that.maxScrollY >= 0) ? 0 : that.maxScrollY;
273			}
274
275			if(that.distX + that.distY > 5) {// 5 pixels threshold
276
277				// Lock scroll direction
278				if(that.distX - 3 > that.distY) {
279					newY = that.y;
280					topDelta = 0;
281				} else if(that.distY - 3 > that.distX) {
282					newX = that.x;
283					leftDelta = 0;
284				}
285
286				that.setPosition(newX, newY);
287				that.moved = true;
288				that.directionX = leftDelta > 0 ? -1 : 1;
289				that.directionY = topDelta > 0 ? -1 : 1;
290			} else {
291				that.distX += Math.abs(leftDelta);
292				that.distY += Math.abs(topDelta);
293				//			that.dist+= Math.abs(leftDelta) + Math.abs(topDelta);
294			}
295		},
296		touchEnd : function(e) {
297			if(!this.scrolling) {
298				return;
299			}
300
301			var that = this, time = e.timeStamp - that.scrollStartTime, point = isTouch ? e.changedTouches[0] : e, target, ev, momentumX, momentumY, newDuration = 0, newPositionX = that.x, newPositionY = that.y, snap;
302
303			that.scrolling = false;
304
305			if(!that.moved) {
306				that.resetPosition();
307
308				if(isTouch) {
309					// Find the last touched element
310					target = point.target;
311					while(target.nodeType != 1) {
312						target = target.parentNode;
313					}
314
315					// Create the fake event
316					ev = document.createEvent('MouseEvents');
317					ev.initMouseEvent('click', true, true, e.view, 1, point.screenX, point.screenY, point.clientX, point.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null);
318					ev._fake = true;
319					target.dispatchEvent(ev);
320				}
321
322				return;
323			}
324
325			if(!that.options.snap && time > 250) {// Prevent slingshot effect
326				that.resetPosition();
327				return;
328			}
329
330			if(that.options.momentum) {
331				momentumX = that.scrollX === true ? that.momentum(that.x - that.scrollStartX, time, that.options.bounce ? -that.x + that.scrollWidth / 5 : -that.x, that.options.bounce ? that.x + that.scrollerWidth - that.scrollWidth + that.scrollWidth / 5 : that.x + that.scrollerWidth - that.scrollWidth) : {
332					dist : 0,
333					time : 0
334				};
335				momentumY = that.scrollY === true ? that.momentum(that.y - that.scrollStartY, time, that.options.bounce ? -that.y + that.scrollHeight / 5 : -that.y, that.options.bounce ? (that.maxScrollY < 0 ? that.y + that.scrollerHeight - that.scrollHeight : 0) + that.scrollHeight / 5 : that.y + that.scrollerHeight - that.scrollHeight) : {
336					dist : 0,
337					time : 0
338				};
339				newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1);
340				// The minimum animation length must be 1ms
341				newPositionX = that.x + momentumX.dist;
342				newPositionY = that.y + momentumY.dist;
343			}
344
345			if(that.options.snap) {
346				snap = that.snap(newPositionX, newPositionY);
347				newPositionX = snap.x;
348				newPositionY = snap.y;
349				newDuration = Math.max(snap.time, newDuration);
350			}
351
352			that.scrollTo(newPositionX, newPositionY, newDuration + 'ms');
353		},
354		transitionEnd : function() {
355			var that = this;
356			document.removeEventListener('webkitTransitionEnd', that, false);
357			that.resetPosition();
358		},
359		resetPosition : function() {
360			var that = this, resetX = that.x, resetY = that.y;
361
362			if(that.x >= 0) {
363				resetX = 0;
364			} else if(that.x < that.maxScrollX) {
365				resetX = that.maxScrollX;
366			}
367
368			if(that.y >= 0 || that.maxScrollY > 0) {
369				resetY = 0;
370			} else if(that.y < that.maxScrollY) {
371				resetY = that.maxScrollY;
372			}
373
374			if(resetX != that.x || resetY != that.y) {
375				that.scrollTo(resetX, resetY);
376			} else {
377				if(that.moved) {
378					that.onScrollEnd();
379					// Execute custom code on scroll end
380					that.moved = false;
381				}
382
383				// Hide the scrollbars
384				if(that.scrollBarX) {
385					that.scrollBarX.hide();
386				}
387				if(that.scrollBarY) {
388					that.scrollBarY.hide();
389				}
390			}
391		},
392		snap : function(x, y) {
393			var that = this, time;
394
395			if(that.directionX > 0) {
396				x = Math.floor(x / that.scrollWidth);
397			} else if(that.directionX < 0) {
398				x = Math.ceil(x / that.scrollWidth);
399			} else {
400				x = Math.round(x / that.scrollWidth);
401			}
402			that.pageX = -x;
403			x = x * that.scrollWidth;
404			if(x > 0) {
405				x = that.pageX = 0;
406			} else if(x < that.maxScrollX) {
407				that.pageX = that.maxPageX;
408				x = that.maxScrollX;
409			}
410
411			if(that.directionY > 0) {
412				y = Math.floor(y / that.scrollHeight);
413			} else if(that.directionY < 0) {
414				y = Math.ceil(y / that.scrollHeight);
415			} else {
416				y = Math.round(y / that.scrollHeight);
417			}
418			that.pageY = -y;
419			y = y * that.scrollHeight;
420			if(y > 0) {
421				y = that.pageY = 0;
422			} else if(y < that.maxScrollY) {
423				that.pageY = that.maxPageY;
424				y = that.maxScrollY;
425			}
426
427			// Snap with constant speed (proportional duration)
428			time = Math.round(Math.max(Math.abs(that.x - x) / that.scrollWidth * 500, Math.abs(that.y - y) / that.scrollHeight * 500));
429
430			return {
431				x : x,
432				y : y,
433				time : time
434			};
435		},
436		scrollTo : function(destX, destY, runtime) {
437			var that = this;
438
439			if(that.x == destX && that.y == destY) {
440				that.resetPosition();
441				return;
442			}
443
444			that.moved = true;
445			that.setTransitionTime(runtime || '350ms');
446			that.setPosition(destX, destY);
447
448			if(runtime === '0' || runtime == '0s' || runtime == '0ms') {
449				that.resetPosition();
450			} else {
451				document.addEventListener('webkitTransitionEnd', that, false);
452				// At the end of the transition check if we are still inside of the boundaries
453			}
454		},
455		scrollToPage : function(pageX, pageY, runtime) {
456			var that = this, snap;
457
458			if(!that.options.snap) {
459				that.pageX = -Math.round(that.x / that.scrollWidth);
460				that.pageY = -Math.round(that.y / that.scrollHeight);
461			}
462
463			if(pageX == 'next') {
464				pageX = ++that.pageX;
465			} else if(pageX == 'prev') {
466				pageX = --that.pageX;
467			}
468
469			if(pageY == 'next') {
470				pageY = ++that.pageY;
471			} else if(pageY == 'prev') {
472				pageY = --that.pageY;
473			}
474			pageX = -pageX * that.scrollWidth;
475			pageY = -pageY * that.scrollHeight;
476			snap = that.snap(pageX, pageY);
477			pageX = snap.x;
478			pageY = snap.y;
479
480			that.scrollTo(pageX, pageY, runtime || '500ms');
481		},
482		scrollToElement : function(el, runtime) {
483			el = typeof el == 'object' ? el : this.element.querySelector(el);
484
485			if(!el) {
486				return;
487			}
488
489			var that = this, x = that.scrollX ? -el.offsetLeft : 0, y = that.scrollY ? -el.offsetTop : 0;
490
491			if(x >= 0) {
492				x = 0;
493			} else if(x < that.maxScrollX) {
494				x = that.maxScrollX;
495			}
496
497			if(y >= 0) {
498				y = 0;
499			} else if(y < that.maxScrollY) {
500				y = that.maxScrollY;
501			}
502
503			that.scrollTo(x, y, runtime);
504		},
505		momentum : function(dist, time, maxDistUpper, maxDistLower) {
506			var friction = 2.5, deceleration = 1.2, speed = Math.abs(dist) / time * 1000, newDist = speed * speed / friction / 1000, newTime = 0;
507
508			// Proportinally reduce speed if we are outside of the boundaries
509			if(dist > 0 && newDist > maxDistUpper) {
510				speed = speed * maxDistUpper / newDist / friction;
511				newDist = maxDistUpper;
512			} else if(dist < 0 && newDist > maxDistLower) {
513				speed = speed * maxDistLower / newDist / friction;
514				newDist = maxDistLower;
515			}
516			newDist = newDist * (dist < 0 ? -1 : 1);
517			newTime = speed / deceleration;
518
519			return {
520				dist : Math.round(newDist),
521				time : Math.round(newTime)
522			};
523		},
524		destroy : function(full) {
525			var that = this;
526
527			window.removeEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);
528			that.element.removeEventListener(START_EVENT, that, false);
529			that.element.removeEventListener(MOVE_EVENT, that, false);
530			that.element.removeEventListener(END_EVENT, that, false);
531			document.removeEventListener('webkitTransitionEnd', that, false);
532
533			if(that.options.checkDOMChanges) {
534				that.element.removeEventListener('DOMSubtreeModified', that, false);
535			}
536
537			if(that.scrollBarX) {
538				that.scrollBarX = that.scrollBarX.remove();
539			}
540
541			if(that.scrollBarY) {
542				that.scrollBarY = that.scrollBarY.remove();
543			}
544
545			if(full) {
546				that.wrapper.parentNode.removeChild(that.wrapper);
547			}
548
549			return null;
550		}
551	};
552
553	function scrollbar(dir, wrapper, fade, shrink, color) {
554		var that = this, doc = document;
555
556		that.dir = dir;
557		that.fade = fade;
558		that.shrink = shrink;
559		that.uid = ++uid;
560
561		// Create main scrollbar
562		that.bar = doc.createElement('div');
563
564		that.bar.style.cssText = 'position:absolute;top:0;left:0;-webkit-transition-timing-function:cubic-bezier(0,0,0.25,1);pointer-events:none;-webkit-transition-duration:0;-webkit-transition-delay:0;-webkit-transition-property:-webkit-transform;z-index:10;background:' + color + ';' + '-webkit-transform:' + translateOpen + '0,0' + translateClose + ';' + (dir == 'horizontal' ? '-webkit-border-radius:3px 2px;min-width:6px;min-height:5px' : '-webkit-border-radius:2px 3px;min-width:5px;min-height:6px');
565
566		// Create scrollbar wrapper
567		that.wrapper = doc.createElement('div');
568		that.wrapper.style.cssText = '-webkit-mask:-webkit-canvas(scrollbar' + that.uid + that.dir + ');position:absolute;z-index:10;pointer-events:none;overflow:hidden;opacity:0;-webkit-transition-duration:' + ( fade ? '300ms' : '0') + ';-webkit-transition-delay:0;-webkit-transition-property:opacity;' + (that.dir == 'horizontal' ? 'bottom:2px;left:2px;right:7px;height:5px' : 'top:2px;right:2px;bottom:7px;width:5px;');
569
570		// Add scrollbar to the DOM
571		that.wrapper.appendChild(that.bar);
572		wrapper.appendChild(that.wrapper);
573	}
574
575
576	scrollbar.prototype = {
577		init : function(scroll, size) {
578			var that = this, doc = document, pi = Math.PI, ctx;
579
580			// Create scrollbar mask
581			if(that.dir == 'horizontal') {
582				if(that.maxSize != that.wrapper.offsetWidth) {
583					that.maxSize = that.wrapper.offsetWidth;
584					ctx = doc.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, that.maxSize, 5);
585					ctx.fillStyle = "rgb(0,0,0)";
586					ctx.beginPath();
587					ctx.arc(2.5, 2.5, 2.5, pi / 2, -pi / 2, false);
588					ctx.lineTo(that.maxSize - 2.5, 0);
589					ctx.arc(that.maxSize - 2.5, 2.5, 2.5, -pi / 2, pi / 2, false);
590					ctx.closePath();
591					ctx.fill();
592				}
593			} else {
594				if(that.maxSize != that.wrapper.offsetHeight) {
595					that.maxSize = that.wrapper.offsetHeight;
596					ctx = doc.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, 5, that.maxSize);
597					ctx.fillStyle = "rgb(0,0,0)";
598					ctx.beginPath();
599					ctx.arc(2.5, 2.5, 2.5, pi, 0, false);
600					ctx.lineTo(5, that.maxSize - 2.5);
601					ctx.arc(2.5, that.maxSize - 2.5, 2.5, 0, pi, false);
602					ctx.closePath();
603					ctx.fill();
604				}
605			}
606
607			that.size = Math.max(Math.round(that.maxSize * that.maxSize / size), 6);
608			that.maxScroll = that.maxSize - that.size;
609			that.toWrapperProp = that.maxScroll / (scroll - size);
610			that.bar.style[that.dir == 'horizontal' ? 'width' : 'height'] = that.size + 'px';
611		},
612		setPosition : function(pos) {
613			var that = this;
614
615			if(that.wrapper.style.opacity != '1') {
616				that.show();
617			}
618			pos = Math.round(that.toWrapperProp * pos);
619
620			if(pos < 0) {
621				pos = that.shrink ? pos + pos * 3 : 0;
622				if(that.size + pos < 7) {
623					pos = -that.size + 6;
624				}
625			} else if(pos > that.maxScroll) {
626				pos = that.shrink ? pos + (pos - that.maxScroll) * 3 : that.maxScroll;
627				if(that.size + that.maxScroll - pos < 7) {
628					pos = that.size + that.maxScroll - 6;
629				}
630			}
631			pos = that.dir == 'horizontal' ? translateOpen + pos + 'px,0' + translateClose : translateOpen + '0,' + pos + 'px' + translateClose;
632
633			that.bar.style.webkitTransform = pos;
634		},
635		show : function() {
636			if(has3d) {
637				this.wrapper.style.webkitTransitionDelay = '0';
638			}
639			this.wrapper.style.opacity = '1';
640		},
641		hide : function() {
642			if(has3d) {
643				this.wrapper.style.webkitTransitionDelay = '350ms';
644			}
645			this.wrapper.style.opacity = '0';
646		},
647		remove : function() {
648			this.wrapper.parentNode.removeChild(this.wrapper);
649			return null;
650		}
651	};
652
653	// Is translate3d compatible?
654	var has3d = ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()),
655	// Device sniffing
656	isIthing = (/iphone|ipad/gi).test(navigator.appVersion), isTouch = ('ontouchstart' in window),
657	// Event sniffing
658	START_EVENT = isTouch ? 'touchstart' : 'mousedown', MOVE_EVENT = isTouch ? 'touchmove' : 'mousemove', END_EVENT = isTouch ? 'touchend' : 'mouseup',
659	// Translate3d helper
660	translateOpen = 'translate' + ( has3d ? '3d(' : '('), translateClose = has3d ? ',0)' : ')',
661	// Unique ID
662	uid = 0;
663
664	// Expose iScroll to the world
665	$kit.iScroll = iScroll;
666})();