/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
- /**
- * @ignore
- * Find more about the scrolling function at
- * http://cubiq.org/iscroll
- *
- * Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/
- * Released under MIT license
- * http://cubiq.org/dropbox/mit-license.txt
- *
- * Version 3.7.1 - Last updated: 2010.10.08
- *
- */
- (function() {
- function iScroll(el, options) {
- var that = this, i;
- that.element = typeof el == 'object' ? el : document.getElementById(el);
- that.wrapper = that.element.parentNode;
- that.element.style.webkitTransitionProperty = '-webkit-transform';
- that.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
- that.element.style.webkitTransitionDuration = '0';
- that.element.style.webkitTransform = translateOpen + '0,0' + translateClose;
- // Default options
- that.options = {
- bounce : has3d,
- momentum : has3d,
- checkDOMChanges : true,
- topOnDOMChanges : false,
- hScrollbar : has3d,
- vScrollbar : has3d,
- fadeScrollbar : isIthing || !isTouch,
- shrinkScrollbar : isIthing || !isTouch,
- desktopCompatibility : false,
- overflow : 'auto',
- snap : false,
- bounceLock : false,
- scrollbarColor : 'rgba(0,0,0,0.5)',
- onScrollEnd : function() {
- }
- };
- // User defined options
- if( typeof options == 'object') {
- for(i in options) {
- that.options[i] = options[i];
- }
- }
- if(that.options.desktopCompatibility) {
- that.options.overflow = 'hidden';
- }
- that.onScrollEnd = that.options.onScrollEnd;
- delete that.options.onScrollEnd;
- that.wrapper.style.overflow = that.options.overflow;
- that.refresh();
- window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);
- if(isTouch || that.options.desktopCompatibility) {
- that.element.addEventListener(START_EVENT, that, false);
- that.element.addEventListener(MOVE_EVENT, that, false);
- that.element.addEventListener(END_EVENT, that, false);
- }
- if(that.options.checkDOMChanges) {
- that.element.addEventListener('DOMSubtreeModified', that, false);
- }
- }
- iScroll.prototype = {
- x : 0,
- y : 0,
- enabled : true,
- handleEvent : function(e) {
- var that = this;
- switch (e.type) {
- case START_EVENT:
- that.touchStart(e);
- break;
- case MOVE_EVENT:
- that.touchMove(e);
- break;
- case END_EVENT:
- that.touchEnd(e);
- break;
- case 'webkitTransitionEnd':
- that.transitionEnd();
- break;
- case 'orientationchange':
- case 'resize':
- that.refresh();
- break;
- case 'DOMSubtreeModified':
- that.onDOMModified(e);
- break;
- }
- },
- onDOMModified : function(e) {
- var that = this;
- // (Hopefully) execute onDOMModified only once
- if(e.target.parentNode != that.element) {
- return;
- }
- setTimeout(function() {
- that.refresh();
- }, 0);
- if(that.options.topOnDOMChanges && (that.x != 0 || that.y != 0)) {
- that.scrollTo(0, 0, '0');
- }
- },
- refresh : function() {
- var that = this, resetX = that.x, resetY = that.y, snap;
- that.scrollWidth = that.wrapper.clientWidth;
- that.scrollHeight = that.wrapper.clientHeight;
- that.scrollerWidth = that.element.offsetWidth;
- that.scrollerHeight = that.element.offsetHeight;
- that.maxScrollX = that.scrollWidth - that.scrollerWidth;
- that.maxScrollY = that.scrollHeight - that.scrollerHeight;
- that.directionX = 0;
- that.directionY = 0;
- if(that.scrollX) {
- if(that.maxScrollX >= 0) {
- resetX = 0;
- } else if(that.x < that.maxScrollX) {
- resetX = that.maxScrollX;
- }
- }
- if(that.scrollY) {
- if(that.maxScrollY >= 0) {
- resetY = 0;
- } else if(that.y < that.maxScrollY) {
- resetY = that.maxScrollY;
- }
- }
- // Snap
- if(that.options.snap) {
- that.maxPageX = -Math.floor(that.maxScrollX / that.scrollWidth);
- that.maxPageY = -Math.floor(that.maxScrollY / that.scrollHeight);
- snap = that.snap(resetX, resetY);
- resetX = snap.x;
- resetY = snap.y;
- }
- if(resetX != that.x || resetY != that.y) {
- that.setTransitionTime('0');
- that.setPosition(resetX, resetY, true);
- }
- that.scrollX = that.scrollerWidth > that.scrollWidth;
- that.scrollY = !that.options.bounceLock && !that.scrollX || that.scrollerHeight > that.scrollHeight;
- // Update horizontal scrollbar
- if(that.options.hScrollbar && that.scrollX) {
- that.scrollBarX = that.scrollBarX || new scrollbar('horizontal', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar, that.options.scrollbarColor);
- that.scrollBarX.init(that.scrollWidth, that.scrollerWidth);
- } else if(that.scrollBarX) {
- that.scrollBarX = that.scrollBarX.remove();
- }
- // Update vertical scrollbar
- if(that.options.vScrollbar && that.scrollY && that.scrollerHeight > that.scrollHeight) {
- that.scrollBarY = that.scrollBarY || new scrollbar('vertical', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar, that.options.scrollbarColor);
- that.scrollBarY.init(that.scrollHeight, that.scrollerHeight);
- } else if(that.scrollBarY) {
- that.scrollBarY = that.scrollBarY.remove();
- }
- },
- setPosition : function(x, y, hideScrollBars) {
- var that = this;
- that.x = x;
- that.y = y;
- that.element.style.webkitTransform = translateOpen + that.x + 'px,' + that.y + 'px' + translateClose;
- // Move the scrollbars
- if(!hideScrollBars) {
- if(that.scrollBarX) {
- that.scrollBarX.setPosition(that.x);
- }
- if(that.scrollBarY) {
- that.scrollBarY.setPosition(that.y);
- }
- }
- },
- setTransitionTime : function(time) {
- var that = this;
- time = time || '0';
- that.element.style.webkitTransitionDuration = time;
- if(that.scrollBarX) {
- that.scrollBarX.bar.style.webkitTransitionDuration = time;
- that.scrollBarX.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
- }
- if(that.scrollBarY) {
- that.scrollBarY.bar.style.webkitTransitionDuration = time;
- that.scrollBarY.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
- }
- },
- touchStart : function(e) {
- var that = this, matrix;
- if(!that.enabled) {
- return;
- }
- e.preventDefault();
- e.stopPropagation();
- that.scrolling = true;
- // This is probably not needed, but may be useful if iScroll is used in conjuction with other frameworks
- that.moved = false;
- that.distX = 0;
- that.distY = 0;
- that.setTransitionTime('0');
- // Check if the scroller is really where it should be
- if(that.options.momentum || that.options.snap) {
- matrix = new WebKitCSSMatrix(window.getComputedStyle(that.element).webkitTransform);
- if(matrix.e != that.x || matrix.f != that.y) {
- document.removeEventListener('webkitTransitionEnd', that, false);
- that.setPosition(matrix.e, matrix.f);
- that.moved = true;
- }
- }
- that.touchStartX = isTouch ? e.changedTouches[0].pageX : e.pageX;
- that.scrollStartX = that.x;
- that.touchStartY = isTouch ? e.changedTouches[0].pageY : e.pageY;
- that.scrollStartY = that.y;
- that.scrollStartTime = e.timeStamp;
- that.directionX = 0;
- that.directionY = 0;
- },
- touchMove : function(e) {
- if(!this.scrolling) {
- return;
- }
- 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;
- //e.preventDefault();
- e.stopPropagation();
- // Stopping propagation just saves some cpu cycles (I presume)
- that.touchStartX = pageX;
- that.touchStartY = pageY;
- // Slow down if outside of the boundaries
- if(newX >= 0 || newX < that.maxScrollX) {
- newX = that.options.bounce ? Math.round(that.x + leftDelta / 3) : (newX >= 0 || that.maxScrollX >= 0) ? 0 : that.maxScrollX;
- }
- if(newY >= 0 || newY < that.maxScrollY) {
- newY = that.options.bounce ? Math.round(that.y + topDelta / 3) : (newY >= 0 || that.maxScrollY >= 0) ? 0 : that.maxScrollY;
- }
- if(that.distX + that.distY > 5) {// 5 pixels threshold
- // Lock scroll direction
- if(that.distX - 3 > that.distY) {
- newY = that.y;
- topDelta = 0;
- } else if(that.distY - 3 > that.distX) {
- newX = that.x;
- leftDelta = 0;
- }
- that.setPosition(newX, newY);
- that.moved = true;
- that.directionX = leftDelta > 0 ? -1 : 1;
- that.directionY = topDelta > 0 ? -1 : 1;
- } else {
- that.distX += Math.abs(leftDelta);
- that.distY += Math.abs(topDelta);
- // that.dist+= Math.abs(leftDelta) + Math.abs(topDelta);
- }
- },
- touchEnd : function(e) {
- if(!this.scrolling) {
- return;
- }
- 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;
- that.scrolling = false;
- if(!that.moved) {
- that.resetPosition();
- if(isTouch) {
- // Find the last touched element
- target = point.target;
- while(target.nodeType != 1) {
- target = target.parentNode;
- }
- // Create the fake event
- ev = document.createEvent('MouseEvents');
- 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);
- ev._fake = true;
- target.dispatchEvent(ev);
- }
- return;
- }
- if(!that.options.snap && time > 250) {// Prevent slingshot effect
- that.resetPosition();
- return;
- }
- if(that.options.momentum) {
- 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) : {
- dist : 0,
- time : 0
- };
- 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) : {
- dist : 0,
- time : 0
- };
- newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1);
- // The minimum animation length must be 1ms
- newPositionX = that.x + momentumX.dist;
- newPositionY = that.y + momentumY.dist;
- }
- if(that.options.snap) {
- snap = that.snap(newPositionX, newPositionY);
- newPositionX = snap.x;
- newPositionY = snap.y;
- newDuration = Math.max(snap.time, newDuration);
- }
- that.scrollTo(newPositionX, newPositionY, newDuration + 'ms');
- },
- transitionEnd : function() {
- var that = this;
- document.removeEventListener('webkitTransitionEnd', that, false);
- that.resetPosition();
- },
- resetPosition : function() {
- var that = this, resetX = that.x, resetY = that.y;
- if(that.x >= 0) {
- resetX = 0;
- } else if(that.x < that.maxScrollX) {
- resetX = that.maxScrollX;
- }
- if(that.y >= 0 || that.maxScrollY > 0) {
- resetY = 0;
- } else if(that.y < that.maxScrollY) {
- resetY = that.maxScrollY;
- }
- if(resetX != that.x || resetY != that.y) {
- that.scrollTo(resetX, resetY);
- } else {
- if(that.moved) {
- that.onScrollEnd();
- // Execute custom code on scroll end
- that.moved = false;
- }
- // Hide the scrollbars
- if(that.scrollBarX) {
- that.scrollBarX.hide();
- }
- if(that.scrollBarY) {
- that.scrollBarY.hide();
- }
- }
- },
- snap : function(x, y) {
- var that = this, time;
- if(that.directionX > 0) {
- x = Math.floor(x / that.scrollWidth);
- } else if(that.directionX < 0) {
- x = Math.ceil(x / that.scrollWidth);
- } else {
- x = Math.round(x / that.scrollWidth);
- }
- that.pageX = -x;
- x = x * that.scrollWidth;
- if(x > 0) {
- x = that.pageX = 0;
- } else if(x < that.maxScrollX) {
- that.pageX = that.maxPageX;
- x = that.maxScrollX;
- }
- if(that.directionY > 0) {
- y = Math.floor(y / that.scrollHeight);
- } else if(that.directionY < 0) {
- y = Math.ceil(y / that.scrollHeight);
- } else {
- y = Math.round(y / that.scrollHeight);
- }
- that.pageY = -y;
- y = y * that.scrollHeight;
- if(y > 0) {
- y = that.pageY = 0;
- } else if(y < that.maxScrollY) {
- that.pageY = that.maxPageY;
- y = that.maxScrollY;
- }
- // Snap with constant speed (proportional duration)
- time = Math.round(Math.max(Math.abs(that.x - x) / that.scrollWidth * 500, Math.abs(that.y - y) / that.scrollHeight * 500));
- return {
- x : x,
- y : y,
- time : time
- };
- },
- scrollTo : function(destX, destY, runtime) {
- var that = this;
- if(that.x == destX && that.y == destY) {
- that.resetPosition();
- return;
- }
- that.moved = true;
- that.setTransitionTime(runtime || '350ms');
- that.setPosition(destX, destY);
- if(runtime === '0' || runtime == '0s' || runtime == '0ms') {
- that.resetPosition();
- } else {
- document.addEventListener('webkitTransitionEnd', that, false);
- // At the end of the transition check if we are still inside of the boundaries
- }
- },
- scrollToPage : function(pageX, pageY, runtime) {
- var that = this, snap;
- if(!that.options.snap) {
- that.pageX = -Math.round(that.x / that.scrollWidth);
- that.pageY = -Math.round(that.y / that.scrollHeight);
- }
- if(pageX == 'next') {
- pageX = ++that.pageX;
- } else if(pageX == 'prev') {
- pageX = --that.pageX;
- }
- if(pageY == 'next') {
- pageY = ++that.pageY;
- } else if(pageY == 'prev') {
- pageY = --that.pageY;
- }
- pageX = -pageX * that.scrollWidth;
- pageY = -pageY * that.scrollHeight;
- snap = that.snap(pageX, pageY);
- pageX = snap.x;
- pageY = snap.y;
- that.scrollTo(pageX, pageY, runtime || '500ms');
- },
- scrollToElement : function(el, runtime) {
- el = typeof el == 'object' ? el : this.element.querySelector(el);
- if(!el) {
- return;
- }
- var that = this, x = that.scrollX ? -el.offsetLeft : 0, y = that.scrollY ? -el.offsetTop : 0;
- if(x >= 0) {
- x = 0;
- } else if(x < that.maxScrollX) {
- x = that.maxScrollX;
- }
- if(y >= 0) {
- y = 0;
- } else if(y < that.maxScrollY) {
- y = that.maxScrollY;
- }
- that.scrollTo(x, y, runtime);
- },
- momentum : function(dist, time, maxDistUpper, maxDistLower) {
- var friction = 2.5, deceleration = 1.2, speed = Math.abs(dist) / time * 1000, newDist = speed * speed / friction / 1000, newTime = 0;
- // Proportinally reduce speed if we are outside of the boundaries
- if(dist > 0 && newDist > maxDistUpper) {
- speed = speed * maxDistUpper / newDist / friction;
- newDist = maxDistUpper;
- } else if(dist < 0 && newDist > maxDistLower) {
- speed = speed * maxDistLower / newDist / friction;
- newDist = maxDistLower;
- }
- newDist = newDist * (dist < 0 ? -1 : 1);
- newTime = speed / deceleration;
- return {
- dist : Math.round(newDist),
- time : Math.round(newTime)
- };
- },
- destroy : function(full) {
- var that = this;
- window.removeEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);
- that.element.removeEventListener(START_EVENT, that, false);
- that.element.removeEventListener(MOVE_EVENT, that, false);
- that.element.removeEventListener(END_EVENT, that, false);
- document.removeEventListener('webkitTransitionEnd', that, false);
- if(that.options.checkDOMChanges) {
- that.element.removeEventListener('DOMSubtreeModified', that, false);
- }
- if(that.scrollBarX) {
- that.scrollBarX = that.scrollBarX.remove();
- }
- if(that.scrollBarY) {
- that.scrollBarY = that.scrollBarY.remove();
- }
- if(full) {
- that.wrapper.parentNode.removeChild(that.wrapper);
- }
- return null;
- }
- };
- function scrollbar(dir, wrapper, fade, shrink, color) {
- var that = this, doc = document;
- that.dir = dir;
- that.fade = fade;
- that.shrink = shrink;
- that.uid = ++uid;
- // Create main scrollbar
- that.bar = doc.createElement('div');
- 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');
- // Create scrollbar wrapper
- that.wrapper = doc.createElement('div');
- 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;');
- // Add scrollbar to the DOM
- that.wrapper.appendChild(that.bar);
- wrapper.appendChild(that.wrapper);
- }
- scrollbar.prototype = {
- init : function(scroll, size) {
- var that = this, doc = document, pi = Math.PI, ctx;
- // Create scrollbar mask
- if(that.dir == 'horizontal') {
- if(that.maxSize != that.wrapper.offsetWidth) {
- that.maxSize = that.wrapper.offsetWidth;
- ctx = doc.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, that.maxSize, 5);
- ctx.fillStyle = "rgb(0,0,0)";
- ctx.beginPath();
- ctx.arc(2.5, 2.5, 2.5, pi / 2, -pi / 2, false);
- ctx.lineTo(that.maxSize - 2.5, 0);
- ctx.arc(that.maxSize - 2.5, 2.5, 2.5, -pi / 2, pi / 2, false);
- ctx.closePath();
- ctx.fill();
- }
- } else {
- if(that.maxSize != that.wrapper.offsetHeight) {
- that.maxSize = that.wrapper.offsetHeight;
- ctx = doc.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, 5, that.maxSize);
- ctx.fillStyle = "rgb(0,0,0)";
- ctx.beginPath();
- ctx.arc(2.5, 2.5, 2.5, pi, 0, false);
- ctx.lineTo(5, that.maxSize - 2.5);
- ctx.arc(2.5, that.maxSize - 2.5, 2.5, 0, pi, false);
- ctx.closePath();
- ctx.fill();
- }
- }
- that.size = Math.max(Math.round(that.maxSize * that.maxSize / size), 6);
- that.maxScroll = that.maxSize - that.size;
- that.toWrapperProp = that.maxScroll / (scroll - size);
- that.bar.style[that.dir == 'horizontal' ? 'width' : 'height'] = that.size + 'px';
- },
- setPosition : function(pos) {
- var that = this;
- if(that.wrapper.style.opacity != '1') {
- that.show();
- }
- pos = Math.round(that.toWrapperProp * pos);
- if(pos < 0) {
- pos = that.shrink ? pos + pos * 3 : 0;
- if(that.size + pos < 7) {
- pos = -that.size + 6;
- }
- } else if(pos > that.maxScroll) {
- pos = that.shrink ? pos + (pos - that.maxScroll) * 3 : that.maxScroll;
- if(that.size + that.maxScroll - pos < 7) {
- pos = that.size + that.maxScroll - 6;
- }
- }
- pos = that.dir == 'horizontal' ? translateOpen + pos + 'px,0' + translateClose : translateOpen + '0,' + pos + 'px' + translateClose;
- that.bar.style.webkitTransform = pos;
- },
- show : function() {
- if(has3d) {
- this.wrapper.style.webkitTransitionDelay = '0';
- }
- this.wrapper.style.opacity = '1';
- },
- hide : function() {
- if(has3d) {
- this.wrapper.style.webkitTransitionDelay = '350ms';
- }
- this.wrapper.style.opacity = '0';
- },
- remove : function() {
- this.wrapper.parentNode.removeChild(this.wrapper);
- return null;
- }
- };
- // Is translate3d compatible?
- var has3d = ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()),
- // Device sniffing
- isIthing = (/iphone|ipad/gi).test(navigator.appVersion), isTouch = ('ontouchstart' in window),
- // Event sniffing
- START_EVENT = isTouch ? 'touchstart' : 'mousedown', MOVE_EVENT = isTouch ? 'touchmove' : 'mousemove', END_EVENT = isTouch ? 'touchend' : 'mouseup',
- // Translate3d helper
- translateOpen = 'translate' + ( has3d ? '3d(' : '('), translateClose = has3d ? ',0)' : ')',
- // Unique ID
- uid = 0;
- // Expose iScroll to the world
- $kit.iScroll = iScroll;
- })();