From: ISHIKAWA Mutsumi Date: Wed, 25 Aug 2010 10:35:31 +0000 (+0900) Subject: update iscroll.js v3.6 base X-Git-Url: http://git.osdn.net/view?p=keitairc%2Fkeitairc.git;a=commitdiff_plain;h=cbd3bffb5898855b6ffaec069b9da19d481aace1 update iscroll.js v3.6 base --- diff --git a/data/public/iscroll/iscroll.js b/data/public/iscroll/iscroll.js index e7d56d6..8bb6f48 100644 --- a/data/public/iscroll/iscroll.js +++ b/data/public/iscroll/iscroll.js @@ -1,419 +1,724 @@ /** - * + * * Find more about the scrolling function at - * http://cubiq.org/scrolling-div-for-mobile-webkit-turns-3/16 + * http://cubiq.org/iscroll * - * Copyright (c) 2009 Matteo Spinelli, http://cubiq.org/ + * Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/ * Released under MIT license * http://cubiq.org/dropbox/mit-license.txt - * - * Version 3.2.1 - Last updated: 2010.06.03 - * + * + * Version 3.6 - Last updated: 2010.08.24 + * */ (function(){ - function iScroll (el, options) { - this.element = typeof el == 'object' ? el : document.getElementById(el); - this.wrapper = this.element.parentNode; - - this.element.style.webkitTransitionProperty = '-webkit-transform'; - this.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)'; - this.element.style.webkitTransitionDuration = '0'; - this.element.style.webkitTransform = 'translate3d(0,0,0)'; - - // Get options - this.options = { - bounce: true, + 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: true, - vScrollBar: true, - min_move: 5 + hScrollbar: has3d, + vScrollbar: has3d, + fadeScrollbar: isIphone || isIpad || !isTouch, + shrinkScrollbar: isIphone || isIpad || !isTouch, + desktopCompatibility: false, + overflow: 'hidden', + snap: false }; - + + // User defined options if (typeof options == 'object') { - for (var i in options) { - this.options[i] = options[i]; + for (i in options) { + that.options[i] = options[i]; } } - this.refresh(); - - this.element.addEventListener('touchstart', this); - this.element.addEventListener('touchmove', this); - this.element.addEventListener('touchend', this); - window.addEventListener('orientationchange', this); + if (that.options.desktopCompatibility) { + that.options.overflow = 'hidden'; + } + + that.wrapper.style.overflow = that.options.overflow; - if (this.options.checkDOMChanges) { - this.element.addEventListener('DOMSubtreeModified', this); + 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 'touchstart': this.onTouchStart(e); break; - case 'touchmove': this.onTouchMove(e); break; - case 'touchend': this.onTouchEnd(e); break; - case 'webkitTransitionEnd': this.onTransitionEnd(e); break; - case 'orientationchange': this.refresh(); break; - case 'DOMSubtreeModified': this.onDOMModified(e); break; + 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) { - this.refresh(); - - if (this.options.topOnDOMChanges && (this.x!=0 || this.y!=0)) { - this.scrollTo(0,0,'0'); + 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 () { - this.scrollWidth = this.wrapper.clientWidth; - this.scrollHeight = this.wrapper.clientHeight; - this.maxScrollX = this.scrollWidth - this.element.offsetWidth; - this.maxScrollY = this.scrollHeight - this.element.offsetHeight; - - var resetX = this.x, resetY = this.y; - if (this.scrollX) { - if (this.maxScrollX >= 0) { + var that = this, + resetX = this.x, resetY = this.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 (this.x < this.maxScrollX) { - resetX = this.maxScrollX; + } else if (that.x < that.maxScrollX) { + resetX = that.maxScrollX; } } - if (this.scrollY) { - if (this.maxScrollY >= 0) { + if (that.scrollY) { + if (that.maxScrollY >= 0) { resetY = 0; - } else if (this.y < this.maxScrollY) { - resetY = this.maxScrollY; + } else if (that.y < that.maxScrollY) { + resetY = that.maxScrollY; } } - if (resetX!=this.x || resetY!=this.y) { - this.scrollTo(resetX,resetY,'0'); + // 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); } - this.scrollX = this.element.offsetWidth > this.scrollWidth ? true : false; - this.scrollY = this.element.offsetHeight > this.scrollHeight ? true : false; + that.scrollX = that.scrollerWidth > that.scrollWidth; + that.scrollY = !that.scrollX || that.scrollerHeight > that.scrollHeight; // Update horizontal scrollbar - if (this.options.hScrollBar && this.scrollX) { - this.scrollBarX = (this.scrollBarX instanceof scrollbar) ? this.scrollBarX : new scrollbar('horizontal', this.wrapper); - this.scrollBarX.init(this.scrollWidth, this.element.offsetWidth); - } else if (this.scrollBarX) { - this.scrollBarX = this.scrollBarX.remove(); + if (that.options.hScrollbar && that.scrollX) { + that.scrollBarX = that.scrollBarX || new scrollbar('horizontal', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar); + that.scrollBarX.init(that.scrollWidth, that.scrollerWidth); + } else if (that.scrollBarX) { + that.scrollBarX = that.scrollBarX.remove(); } // Update vertical scrollbar - if (this.options.vScrollBar && this.scrollY) { - this.scrollBarY = (this.scrollBarY instanceof scrollbar) ? this.scrollBarY : new scrollbar('vertical', this.wrapper); - this.scrollBarY.init(this.scrollHeight, this.element.offsetHeight); - } else if (this.scrollBarY) { - this.scrollBarY = this.scrollBarY.remove(); + 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.scrollBarY.init(that.scrollHeight, that.scrollerHeight); + } else if (that.scrollBarY) { + that.scrollBarY = that.scrollBarY.remove(); } }, - setPosition: function (x, y) { - this.x = x !== null ? x : this.x; - this.y = y !== null ? y : this.y; + setPosition: function (x, y, hideScrollBars) { + var that = this; - this.element.style.webkitTransform = 'translate3d(' + this.x + 'px,' + this.y + 'px,0)'; + that.x = x; + that.y = y; + + that.element.style.webkitTransform = translateOpen + that.x + 'px,' + that.y + 'px' + translateClose; // Move the scrollbars - if (this.scrollBarX) { - this.scrollBarX.setPosition(this.scrollBarX.maxScroll / this.maxScrollX * this.x); + 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 (this.scrollBarY) { - this.scrollBarY.setPosition(this.scrollBarY.maxScroll / this.maxScrollY * this.y); + if (that.scrollBarY) { + that.scrollBarY.bar.style.webkitTransitionDuration = time; + that.scrollBarY.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0'; } }, - - onTouchStart: function(e) { - if (e.targetTouches.length != 1) { - return false; - } - - /* iOS 4 以外 特に androidだと ここで preventDefault しないと - まともにスクロールしない/ブラウザがスクロールしてしまう/ - スクロールしたあとの位置にあるリンクがクリックされちゃうとか - いろいろ起きる。逆に iOS4だと ここで preventDefault しちゃうと - 長押ししたときの ディフォルトのポップアップが出てこなくなる */ - /* iOS4 意外だと ディフォルトのポップアップが使えなくなるが どうやら 打つ手なし... */ - if (!navigator.appVersion.match(/iPhone OS 4/)) { + + touchStart: function(e) { + var that = this, + matrix; + + /* iOS 4 以外 特に androidだと ここで preventDefault しないと + まともにスクロールしない/ブラウザがスクロールしてしまう/ + スクロールしたあとの位置にあるリンクがクリックされちゃうとか + いろいろ起きる。逆に iOS4だと ここで preventDefault しちゃうと + 長押ししたときの ディフォルトのポップアップが出てこなくなる */ + /* iOS4 意外だと ディフォルトのポップアップが使えなくなるが どうやら打つ手なし... */ + if (!isIphone && !isIpad) { e.preventDefault(); } e.stopPropagation(); - - this.element.style.webkitTransitionDuration = '0'; - - if (this.scrollBarX) { - this.scrollBarX.bar.style.webkitTransitionDuration = '0, 250ms'; - } - if (this.scrollBarY) { - this.scrollBarY.bar.style.webkitTransitionDuration = '0, 250ms'; + + if (!that.enabled) { + return; } - // Check if elem is really where it should be - var theTransform = new WebKitCSSMatrix(window.getComputedStyle(this.element).webkitTransform); - if (theTransform.m41 != this.x || theTransform.m42 != this.y) { - this.setPosition(theTransform.m41, theTransform.m42); + 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.dist = 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; + } } - this.touchStartX = e.touches[0].pageX; - this.scrollStartX = this.x; + that.touchStartX = isTouch ? e.changedTouches[0].pageX : e.pageX; + that.scrollStartX = that.x; - this.touchStartY = e.touches[0].pageY; - this.scrollStartY = this.y; + that.touchStartY = isTouch ? e.changedTouches[0].pageY : e.pageY; + that.scrollStartY = that.y; - this.scrollStartTime = e.timeStamp; - this.moved = false; + that.scrollStartTime = e.timeStamp; + + that.directionX = 0; + that.directionY = 0; }, - - onTouchMove: function(e) { - if (e.targetTouches.length != 1) { - return false; + + touchMove: function(e) { + 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; + + if (!that.scrolling) { + return; } - /* touchstart の preventDefault やめて ここに追加すると iPhone4 だといい感じ*/ - if (navigator.appVersion.match(/iPhone OS 4/)) { - e.preventDefault(); - } - var leftDelta = this.scrollX === true ? e.touches[0].pageX - this.touchStartX : 0, - topDelta = this.scrollY === true ? e.touches[0].pageY - this.touchStartY : 0, - newX = this.x + leftDelta, - newY = this.y + topDelta; + /* touchstart の preventDefault やめて ここに追加すると iPhone4 だといい感じ*/ + + if (isIphone || isIpad) { + e.preventDefault(); + } + e.stopPropagation(); // Stopping propagation just saves some cpu cycles (I presume) + + that.touchStartX = pageX; + that.touchStartY = pageY; - if (Math.abs(topDelta) > this.options.min_move || Math.abs(leftDelta) > this.options.min_move) { // Slow down if outside of the boundaries - if (newX > 0 || newX < this.maxScrollX) { - newX = this.options.bounce ? Math.round(this.x + leftDelta / 4) : this.x; + 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 < this.maxScrollY) { - newY = this.options.bounce ? Math.round(this.y + topDelta / 4) : this.y; + 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 (this.scrollBarX && !this.scrollBarX.visible) { - this.scrollBarX.show(); + if (that.dist > 5) { // 5 pixels threshold is needed on Android, but also on iPhone looks more natural + that.setPosition(newX, newY); + that.moved = true; + that.directionX = leftDelta > 0 ? -1 : 1; + that.directionY = topDelta > 0 ? -1 : 1; + } else { + that.dist+= Math.abs(leftDelta) + Math.abs(topDelta); } - if (this.scrollBarY && !this.scrollBarY.visible) { - this.scrollBarY.show(); + }, + + touchEnd: function(e) { + 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; + + if (!that.scrolling) { + return; } + that.scrolling = false; - this.setPosition(newX, newY); + if (!that.moved) { + that.resetPosition(); - this.touchStartX = e.touches[0].pageX; - this.touchStartY = e.touches[0].pageY; - this.moved = true; + /* iOS4 では touchStart イベントは preventDefault() してないので + click イベントを発生させちゃうと 2重にクリックが発生したような + 状態になるので、スルーでいい */ + if (isAndroid) { + // Find the last touched element + target = point.target; + while (target.nodeType != 1) { + target = target.parentNode; + } + + // Create the fake event + target.style.pointerEvents = 'auto'; + 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); + } - // Prevent slingshot effect - if( e.timeStamp-this.scrollStartTime > 250 ) { - this.scrollStartX = this.x; - this.scrollStartY = this.y; - this.scrollStartTime = e.timeStamp; + return; } - } - }, - - onTouchEnd: function(e) { - if (e.targetTouches.length > 0) { - return false; + + if (!that.options.snap && time > 250) { // Prevent slingshot effect + that.resetPosition(); + return; } - if (!this.moved) { - /* iOS4 では touchStart イベントは preventDefault() してないので - click イベントを発生させちゃうと 2重にクリックが発生したような - 状態になるので、スルーでいい */ - if (!navigator.appVersion.match(/iPhone OS 4/)) { - // Find the last touched element - var theTarget = e.changedTouches[0].target; - if (theTarget.nodeType == 3) { - theTarget = theTarget.parentNode; - } - // Create the fake event - var theEvent = document.createEvent('MouseEvents'); - theEvent.initMouseEvent("click", true, true, document.defaultView, - e.detail, e.screenX, e.screenY, e.clientX, e.clientY, - e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, - e.button, e.relatedTarget); - theTarget.dispatchEvent(theEvent); - return false; - } - return true; - } - - var time = e.timeStamp - this.scrollStartTime; - - var momentumX = this.scrollX === true - ? this.momentum(this.x - this.scrollStartX, - time, - this.options.bounce ? -this.x + this.scrollWidth/4 : -this.x, - this.options.bounce ? this.x + this.element.offsetWidth - this.scrollWidth + this.scrollWidth/4 : this.x + this.element.offsetWidth - this.scrollWidth) - : { dist: 0, time: 0 }; - - var momentumY = this.scrollY === true - ? this.momentum(this.y - this.scrollStartY, - time, - this.options.bounce ? -this.y + this.scrollHeight/4 : -this.y, - this.options.bounce ? this.y + this.element.offsetHeight - this.scrollHeight + this.scrollHeight/4 : this.y + this.element.offsetHeight - this.scrollHeight) - : { dist: 0, time: 0 }; - - if (!momentumX.dist && !momentumY.dist) { - this.resetPosition(); - return false; - } - - var newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1); // The minimum animation length must be 1ms - var newPositionX = this.x + momentumX.dist; - var newPositionY = this.y + momentumY.dist; - - this.element.addEventListener('webkitTransitionEnd', this); - - this.scrollTo(newPositionX, newPositionY, newDuration + 'ms'); + 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; + } - // Move the scrollbars - if (this.scrollBarX) { - this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * newPositionX, newDuration + 'ms'); - } - if (this.scrollBarY) { - this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * newPositionY, newDuration + 'ms'); + 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'); }, - - onTransitionEnd: function () { - this.element.removeEventListener('webkitTransitionEnd', this); - this.resetPosition(); + + transitionEnd: function () { + var that = this; + document.removeEventListener('webkitTransitionEnd', that, false); + that.resetPosition(); }, resetPosition: function () { - var resetX = this.x, - resetY = this.y; - - if (this.x >= 0) { + var that = this, + resetX = that.x, + resetY = that.y; + + if (that.x >= 0) { resetX = 0; - } else if (this.x < this.maxScrollX) { - resetX = this.maxScrollX; + } else if (that.x < that.maxScrollX) { + resetX = that.maxScrollX; } - if (this.y >= 0) { + if (that.y >= 0 || that.maxScrollY > 0) { resetY = 0; - } else if (this.y < this.maxScrollY) { - resetY = this.maxScrollY; + } else if (that.y < that.maxScrollY) { + resetY = that.maxScrollY; } - if (resetX != this.x || resetY != this.y) { - this.scrollTo(resetX, resetY, '500ms'); + 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; + } - if (this.scrollBarX && resetX != this.x) { - this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * resetX, '500ms'); - } - if (this.scrollBarY && resetY != this.y) { - this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * resetY, '500ms'); + // Hide the scrollbars + if (that.scrollBarX) { + that.scrollBarX.hide(); + } + if (that.scrollBarY) { + that.scrollBarY.hide(); } } - - // Hide the scrollbars - if (this.scrollBarX) { - this.scrollBarX.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 (this.scrollBarY) { - this.scrollBarY.hide(); + + 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) { - this.element.style.webkitTransitionDuration = runtime || '400ms'; - this.setPosition(destX, destY); + 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 = 0.1, - deceleration = 1.5, + var friction = 2.5, + deceleration = 1.2, speed = Math.abs(dist) / time * 1000, - newDist = speed * speed / (20 * friction) / 1000; + newDist = speed * speed / friction / 1000, + newTime = 0; - // Proportinally reduce speed if we are outside of the boundaries + // Proportinally reduce speed if we are outside of the boundaries if (dist > 0 && newDist > maxDistUpper) { - speed = speed * maxDistUpper / newDist; + speed = speed * maxDistUpper / newDist / friction; newDist = maxDistUpper; - } - if (dist < 0 && newDist > maxDistLower) { - speed = speed * maxDistLower / newDist; + } else if (dist < 0 && newDist > maxDistLower) { + speed = speed * maxDistLower / newDist / friction; newDist = maxDistLower; } - + newDist = newDist * (dist < 0 ? -1 : 1); - - var newTime = speed / deceleration; + newTime = speed / deceleration; return { dist: Math.round(newDist), time: Math.round(newTime) }; + }, + + onScrollEnd: function () {}, + + 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; } }; -var scrollbar = function (dir, wrapper) { - this.dir = dir; - this.bar = document.createElement('div'); - this.bar.className = 'scrollbar ' + dir; - this.bar.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)'; - this.bar.style.webkitTransform = 'translate3d(0,0,0)'; - this.bar.style.webkitTransitionProperty = '-webkit-transform,opacity'; - this.bar.style.webkitTransitionDuration = '0,300ms'; - this.bar.style.pointerEvents = 'none'; - this.bar.style.opacity = '0'; - - wrapper.appendChild(this.bar); +function scrollbar (dir, wrapper, fade, shrink) { + var that = this, style; + + that.dir = dir; + that.fade = fade; + that.shrink = shrink; + that.uid = ++uid; + + // Create main scrollbar + that.bar = document.createElement('div'); + + style = '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:rgba(0,0,0,0.5);' + + '-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'); + + that.bar.setAttribute('style', style); + + // Create scrollbar wrapper + that.wrapper = document.createElement('div'); + style = '-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;'); + that.wrapper.setAttribute('style', style); + + // Add scrollbar to the DOM + that.wrapper.appendChild(that.bar); + wrapper.appendChild(that.wrapper); } scrollbar.prototype = { - size: 0, - maxSize: 0, - maxScroll: 0, - visible: false, - init: function (scroll, size) { - var offset = this.dir == 'horizontal' ? this.bar.offsetWidth - this.bar.clientWidth : this.bar.offsetHeight - this.bar.clientHeight; - this.maxSize = scroll - 8; // 8 = distance from top + distance from bottom - this.size = Math.round(this.maxSize * this.maxSize / size) + offset; - this.maxScroll = this.maxSize - this.size; - this.bar.style[this.dir == 'horizontal' ? 'width' : 'height'] = (this.size - offset) + 'px'; + var that = this, + ctx; + + // Create scrollbar mask + if (that.dir == 'horizontal') { + if (that.maxSize != that.wrapper.offsetWidth) { + that.maxSize = that.wrapper.offsetWidth; + ctx = document.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, Math.PI/2, -Math.PI/2, false); + ctx.lineTo(that.maxSize-2.5, 0); + ctx.arc(that.maxSize-2.5, 2.5, 2.5, -Math.PI/2, Math.PI/2, false); + ctx.closePath(); + ctx.fill(); + } + } else { + if (that.maxSize != that.wrapper.offsetHeight) { + that.maxSize = that.wrapper.offsetHeight; + ctx = document.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, Math.PI, 0, false); + ctx.lineTo(5, that.maxSize-2.5); + ctx.arc(2.5, that.maxSize-2.5, 2.5, 0, Math.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 = 0; - } else if (pos > this.maxScroll) { - pos = this.maxScroll; + 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 = this.dir == 'horizontal' ? 'translate3d(' + Math.round(pos) + 'px,0,0)' : 'translate3d(0,' + Math.round(pos) + 'px,0)'; - this.bar.style.webkitTransform = pos; - }, - - scrollTo: function (pos, runtime) { - this.bar.style.webkitTransitionDuration = (runtime || '400ms') + ',300ms'; - this.setPosition(pos); + pos = that.dir == 'horizontal' + ? translateOpen + pos + 'px,0' + translateClose + : translateOpen + '0,' + pos + 'px' + translateClose; + + that.bar.style.webkitTransform = pos; }, - + show: function () { - this.visible = true; - this.bar.style.opacity = '1'; + if (has3d) { + this.wrapper.style.webkitTransitionDelay = '0'; + } + this.wrapper.style.opacity = '1'; }, hide: function () { - this.visible = false; - this.bar.style.opacity = '0'; + if (has3d) { + this.wrapper.style.webkitTransitionDelay = '350ms'; + } + this.wrapper.style.opacity = '0'; }, - + remove: function () { - this.bar.parentNode.removeChild(this.bar); + this.wrapper.parentNode.removeChild(this.wrapper); return null; } }; +// Is translate3d compatible? +var has3d = ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()), + // Device sniffing + isIphone = (/iphone/gi).test(navigator.appVersion), + isIpad = (/ipad/gi).test(navigator.appVersion), + isAndroid = (/android/gi).test(navigator.appVersion), + isTouch = isIphone || isIpad || isAndroid, + // 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 window.iScroll = iScroll; })();