From e93422547f0d9dac0c7741730ff52fedafcc9bde Mon Sep 17 00:00:00 2001 From: Alan Knowles Date: Mon, 15 Aug 2011 10:58:33 +0000 Subject: [PATCH] Experimental iscroll - update to newer version. --- ux/Iscroll.js | 1749 +++++++++++++++++++++++++++++-------------------- 1 file changed, 1044 insertions(+), 705 deletions(-) diff --git a/ux/Iscroll.js b/ux/Iscroll.js index 73fc02deb6..12b257d9bd 100644 --- a/ux/Iscroll.js +++ b/ux/Iscroll.js @@ -1,719 +1,1058 @@ -/** - * - * 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.6 - Last updated: 2010.08.24 - * +/*! + * iScroll v4.1.9 ~ Copyright (c) 2011 Matteo Spinelli, http://cubiq.org + * Released under MIT license, http://cubiq.org/license */ -// does not appear to work yet... - - -Roo.namespace('Roo.ux'); - -Roo.ux.Iscroll = function(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 = Roo.ux.Iscroll.translateOpen + '0,0' + Roo.ux.Iscroll.translateClose; - - // Default options - that.options = { - bounce: Roo.ux.Iscroll.has3d, - momentum: Roo.ux.Iscroll.has3d, - checkDOMChanges: true, - topOnDOMChanges: false, - hScrollbar: Roo.ux.Iscroll.has3d, - vScrollbar: Roo.ux.Iscroll.has3d, - fadeScrollbar: Roo.ux.Iscroll.isIphone || Roo.ux.Iscroll.isIpad || !Roo.ux.Iscroll.isTouch, - shrinkScrollbar: Roo.ux.Iscroll.isIphone || Roo.ux.Iscroll.isIpad || !Roo.ux.Iscroll.isTouch, - desktopCompatibility: false, - overflow: 'hidden', - snap: false - }; - - // 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.wrapper.style.overflow = that.options.overflow; - - that.refresh(); +// should we even bother...??? +Roo.namespace('Roo.ux'); - window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false); - - if (Roo.ux.Iscroll.isTouch || that.options.desktopCompatibility) { - that.element.addEventListener(Roo.ux.Iscroll.START_EVENT, that, false); - that.element.addEventListener(Roo.ux.Iscroll.MOVE_EVENT, that, false); - that.element.addEventListener(Roo.ux.Iscroll.END_EVENT, that, false); - } +(function(){ + var m = Math, + vendor = (/webkit/i).test(navigator.appVersion) ? 'webkit' : + (/firefox/i).test(navigator.userAgent) ? 'Moz' : + 'opera' in window ? 'O' : '', - if (that.options.checkDOMChanges) { - that.element.addEventListener('DOMSubtreeModified', that, false); - } -}; - -Roo.apply(Roo.ux.Iscroll, { - 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) -}); - -Roo.apply(Roo.ux.Iscroll, { - isTouch : Roo.ux.Iscroll.isIphone || Roo.ux.Iscroll.isIpad || Roo.ux.Iscroll.isAndroid, - // Event sniffing - START_EVENT : Roo.ux.Iscroll.isTouch ? 'touchstart' : 'mousedown', - MOVE_EVENT : Roo.ux.Iscroll.isTouch ? 'touchmove' : 'mousemove', - END_EVENT : Roo.ux.Iscroll.isTouch ? 'touchend' : 'mouseup', - // Translate3d helper - translateOpen : 'translate' + (Roo.ux.Iscroll.has3d ? '3d(' : '('), - translateClose : Roo.ux.Iscroll.has3d ? ',0)' : ')', - // Unique ID - uid : 0 -}); - - -Roo.ux.Iscroll.prototype = { - x: 0, - y: 0, - enabled: true, - - handleEvent: function (e) { - var that = this; - - switch (e.type) { - case Roo.ux.Iscroll.START_EVENT: - that.touchStart(e); - break; - case Roo.ux.Iscroll.MOVE_EVENT: - that.touchMove(e); - break; - case Roo.ux.Iscroll.END_EVENT: - that.touchEnd(e); - break; - case 'webkitTransitionEnd': - that.transitionEnd(); - break; - case 'orientationchange': - case 'resize': - that.refresh(); - break; - case 'DOMSubtreeModified': - that.onDOMModified(e); - break; - } - }, + // Browser capabilities + has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(), + hasTouch = 'ontouchstart' in window, + hasTransform = vendor + 'Transform' in document.documentElement.style, + isAndroid = (/android/gi).test(navigator.appVersion), + isIDevice = (/iphone|ipad/gi).test(navigator.appVersion), + isPlaybook = (/playbook/gi).test(navigator.appVersion), + hasTransitionEnd = isIDevice || isPlaybook, + nextFrame = (function() { + return window.requestAnimationFrame + || window.webkitRequestAnimationFrame + || window.mozRequestAnimationFrame + || window.oRequestAnimationFrame + || window.msRequestAnimationFrame + || function(callback) { return setTimeout(callback, 1); } + })(), + cancelFrame = (function () { + return window.cancelRequestAnimationFrame + || window.webkitCancelRequestAnimationFrame + || window.mozCancelRequestAnimationFrame + || window.oCancelRequestAnimationFrame + || window.msCancelRequestAnimationFrame + || clearTimeout + })(), - 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 = 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 (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.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.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.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 = Roo.ux.Iscroll.translateOpen + that.x + 'px,' + that.y + 'px' + Roo.ux.Iscroll.translateClose; - - // Move the scrollbars - if (!hideScrollBars) { - if (that.scrollBarX) { - that.scrollBarX.setPosition(that.x); - } - if (that.scrollBarY) { - that.scrollBarY.setPosition(that.y); - } - } - }, + // Events + RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize', + START_EV = hasTouch ? 'touchstart' : 'mousedown', + MOVE_EV = hasTouch ? 'touchmove' : 'mousemove', + END_EV = hasTouch ? 'touchend' : 'mouseup', + CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup', + WHEEL_EV = vendor == 'Moz' ? 'DOMMouseScroll' : 'mousewheel', - setTransitionTime: function(time) { - var that = this; - - time = time || '0'; - that.element.style.webkitTransitionDuration = time; + // Helpers + trnOpen = 'translate' + (has3d ? '3d(' : '('), + trnClose = has3d ? ',0)' : ')'; + + // Constructor +Roo.ux.iScroll = function (el, options) { + var that = this, + doc = document, + i; + + + that.wrapper = typeof el == 'object' ? el : doc.getElementById(el); - if (that.scrollBarX) { - that.scrollBarX.bar.style.webkitTransitionDuration = time; - that.scrollBarX.wrapper.style.webkitTransitionDuration = Roo.ux.Iscroll.has3d && that.options.fadeScrollbar ? '300ms' : '0'; - } - if (that.scrollBarY) { - that.scrollBarY.bar.style.webkitTransitionDuration = time; - that.scrollBarY.wrapper.style.webkitTransitionDuration = Roo.ux.Iscroll.has3d && that.options.fadeScrollbar ? '300ms' : '0'; - } - }, - - touchStart: function(e) { - var that = this, - matrix; - - e.preventDefault(); - e.stopPropagation(); + that.wrapper.style.overflow = 'hidden'; + that.scroller = that.wrapper.children[0]; - if (!that.enabled) { - return; - } - - 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; - } - } - - that.touchStartX = Roo.ux.Iscroll.isTouch ? e.changedTouches[0].pageX : e.pageX; - that.scrollStartX = that.x; - - that.touchStartY = Roo.ux.Iscroll.isTouch ? e.changedTouches[0].pageY : e.pageY; - that.scrollStartY = that.y; - - that.scrollStartTime = e.timeStamp; - - that.directionX = 0; - that.directionY = 0; - }, - - touchMove: function(e) { - var that = this, - pageX = Roo.ux.Iscroll.isTouch ? e.changedTouches[0].pageX : e.pageX, - pageY = Roo.ux.Iscroll.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; - } - - //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.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); - } - }, - - touchEnd: function(e) { - var that = this, - time = e.timeStamp - that.scrollStartTime, - point = Roo.ux.Iscroll.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; - - if (!that.moved) { - that.resetPosition(); - - if (Roo.ux.Iscroll.isTouch) { - // 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); - } - - 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) }; - }, - - 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; - } -}; - -Roo.ux.Iscroll.scrollbar = function (dir, wrapper, fade, shrink) { - var that = this, style; - - that.dir = dir; - that.fade = fade; - that.shrink = shrink; - that.uid = ++Roo.ux.Iscroll.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:' + Roo.ux.Iscroll.translateOpen + '0,0' + Roo.ux.Iscroll.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); -} - -Roo.ux.Iscroll.scrollbar.prototype = { - init: function (scroll, size) { - 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 = 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' - ? Roo.ux.Iscroll.translateOpen + pos + 'px,0' + Roo.ux.Iscroll.translateClose - : Roo.ux.Iscroll.translateOpen + '0,' + pos + 'px' + Roo.ux.Iscroll.translateClose; - - that.bar.style.webkitTransform = pos; - }, - - show: function () { - if (Roo.ux.Iscroll.has3d) { - this.wrapper.style.webkitTransitionDelay = '0'; - } - this.wrapper.style.opacity = '1'; - }, - - hide: function () { - if (Roo.ux.Iscroll.has3d) { - this.wrapper.style.webkitTransitionDelay = '350ms'; - } - this.wrapper.style.opacity = '0'; - }, - - remove: function () { - this.wrapper.parentNode.removeChild(this.wrapper); - return null; - } + // Default options + that.options = { + hScroll: true, + vScroll: true, + bounce: true, + bounceLock: false, + momentum: true, + lockDirection: true, + useTransform: true, + useTransition: false, + topOffset: 0, + checkDOMChanges: false, // Experimental + + // Scrollbar + hScrollbar: true, + vScrollbar: true, + fixedScrollbar: isAndroid, + hideScrollbar: isIDevice, + fadeScrollbar: isIDevice && has3d, + scrollbarClass: '', + + // Zoom + zoom: false, + zoomMin: 1, + zoomMax: 4, + doubleTapZoom: 2, + wheelAction: 'scroll', + + // Snap + snap: false, + snapThreshold: 1, + + // Events + onRefresh: null, + onBeforeScrollStart: function (e) { e.preventDefault(); }, + onScrollStart: null, + onBeforeScrollMove: null, + onScrollMove: null, + onBeforeScrollEnd: null, + onScrollEnd: null, + onTouchEnd: null, + onDestroy: null, + onZoomStart: null, + onZoom: null, + onZoomEnd: null + }; + + // User defined options + for (i in options) that.options[i] = options[i]; + + // Normalize options + that.options.useTransform = hasTransform ? that.options.useTransform : false; + that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar; + that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar; + that.options.zoom = that.options.useTransform && that.options.zoom; + that.options.useTransition = hasTransitionEnd && that.options.useTransition; + + // Set some default styles + that.scroller.style[vendor + 'TransitionProperty'] = that.options.useTransform ? '-' + vendor.toLowerCase() + '-transform' : 'top left'; + that.scroller.style[vendor + 'TransitionDuration'] = '0'; + that.scroller.style[vendor + 'TransformOrigin'] = '0 0'; + if (that.options.useTransition) that.scroller.style[vendor + 'TransitionTimingFunction'] = 'cubic-bezier(0.33,0.66,0.66,1)'; + + if (that.options.useTransform) that.scroller.style[vendor + 'Transform'] = trnOpen + '0,0' + trnClose; + else that.scroller.style.cssText += ';position:absolute;top:0;left:0'; + + if (that.options.useTransition) that.options.fixedScrollbar = true; + + that.refresh(); + + that._bind(RESIZE_EV, window); + that._bind(START_EV); + if (!hasTouch) { + that._bind('mouseout', that.wrapper); + that._bind(WHEEL_EV); + } + + if (that.options.checkDOMChanges) that.checkDOMTime = setInterval(function () { + that._checkDOMChanges(); + }, 500); + }; + +// Prototype +Roo.ux.iScroll.prototype = { + enabled: true, + x: 0, + y: 0, + steps: [], + scale: 1, + currPageX: 0, currPageY: 0, + pagesX: [], pagesY: [], + aniTime: null, + wheelZoomCount: 0, + + handleEvent: function (e) { + var that = this; + switch(e.type) { + case START_EV: + if (!hasTouch && e.button !== 0) return; + that._start(e); + break; + case MOVE_EV: that._move(e); break; + case END_EV: + case CANCEL_EV: that._end(e); break; + case RESIZE_EV: that._resize(); break; + case WHEEL_EV: that._wheel(e); break; + case 'mouseout': that._mouseout(e); break; + case 'webkitTransitionEnd': that._transitionEnd(e); break; + } + }, + + _checkDOMChanges: function () { + if (this.moved || this.zoomed || this.animating || + (this.scrollerW == this.scroller.offsetWidth * this.scale && this.scrollerH == this.scroller.offsetHeight * this.scale)) return; + + this.refresh(); + }, + + _scrollbar: function (dir) { + var that = this, + doc = document, + bar; + + if (!that[dir + 'Scrollbar']) { + if (that[dir + 'ScrollbarWrapper']) { + if (hasTransform) that[dir + 'ScrollbarIndicator'].style[vendor + 'Transform'] = ''; + that[dir + 'ScrollbarWrapper'].parentNode.removeChild(that[dir + 'ScrollbarWrapper']); + that[dir + 'ScrollbarWrapper'] = null; + that[dir + 'ScrollbarIndicator'] = null; + } + + return; + } + + if (!that[dir + 'ScrollbarWrapper']) { + // Create the scrollbar wrapper + bar = doc.createElement('div'); + + if (that.options.scrollbarClass) bar.className = that.options.scrollbarClass + dir.toUpperCase(); + else bar.style.cssText = 'position:absolute;z-index:100;' + (dir == 'h' ? 'height:7px;bottom:1px;left:2px;right:' + (that.vScrollbar ? '7' : '2') + 'px' : 'width:7px;bottom:' + (that.hScrollbar ? '7' : '2') + 'px;top:2px;right:1px'); + + bar.style.cssText += ';pointer-events:none;-' + + vendor + '-transition-property:opacity;-' + + vendor + '-transition-duration:' + (that.options.fadeScrollbar ? '350ms' : '0') + + ';overflow:hidden;opacity:' + (that.options.hideScrollbar ? '0' : '1'); + + that.wrapper.appendChild(bar); + that[dir + 'ScrollbarWrapper'] = bar; + + // Create the scrollbar indicator + bar = doc.createElement('div'); + if (!that.options.scrollbarClass) { + bar.style.cssText = 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);-' + vendor + '-background-clip:padding-box;-' + vendor + '-box-sizing:border-box;' + (dir == 'h' ? 'height:100%' : 'width:100%') + ';-' + vendor + '-border-radius:3px;border-radius:3px'; + } + bar.style.cssText += ';pointer-events:none;-' + vendor + '-transition-property:-' + vendor + '-transform;-' + vendor + '-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);-' + vendor + '-transition-duration:0;-' + vendor + '-transform:' + trnOpen + '0,0' + trnClose; + if (that.options.useTransition) bar.style.cssText += ';-' + vendor + '-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)'; + + that[dir + 'ScrollbarWrapper'].appendChild(bar); + that[dir + 'ScrollbarIndicator'] = bar; + } + + if (dir == 'h') { + that.hScrollbarSize = that.hScrollbarWrapper.clientWidth; + that.hScrollbarIndicatorSize = m.max(m.round(that.hScrollbarSize * that.hScrollbarSize / that.scrollerW), 8); + that.hScrollbarIndicator.style.width = that.hScrollbarIndicatorSize + 'px'; + that.hScrollbarMaxScroll = that.hScrollbarSize - that.hScrollbarIndicatorSize; + that.hScrollbarProp = that.hScrollbarMaxScroll / that.maxScrollX; + } else { + that.vScrollbarSize = that.vScrollbarWrapper.clientHeight; + that.vScrollbarIndicatorSize = m.max(m.round(that.vScrollbarSize * that.vScrollbarSize / that.scrollerH), 8); + that.vScrollbarIndicator.style.height = that.vScrollbarIndicatorSize + 'px'; + that.vScrollbarMaxScroll = that.vScrollbarSize - that.vScrollbarIndicatorSize; + that.vScrollbarProp = that.vScrollbarMaxScroll / that.maxScrollY; + } + + // Reset position + that._scrollbarPos(dir, true); + }, + + _resize: function () { + var that = this; + setTimeout(function () { that.refresh(); }, isAndroid ? 200 : 0); + }, + + _pos: function (x, y) { + x = this.hScroll ? x : 0; + y = this.vScroll ? y : 0; + + if (this.options.useTransform) { + this.scroller.style[vendor + 'Transform'] = trnOpen + x + 'px,' + y + 'px' + trnClose + ' scale(' + this.scale + ')'; + } else { + x = m.round(x); + y = m.round(y); + this.scroller.style.left = x + 'px'; + this.scroller.style.top = y + 'px'; + } + + this.x = x; + this.y = y; + + this._scrollbarPos('h'); + this._scrollbarPos('v'); + }, + + _scrollbarPos: function (dir, hidden) { + var that = this, + pos = dir == 'h' ? that.x : that.y, + size; + + if (!that[dir + 'Scrollbar']) return; + + pos = that[dir + 'ScrollbarProp'] * pos; + + if (pos < 0) { + if (!that.options.fixedScrollbar) { + size = that[dir + 'ScrollbarIndicatorSize'] + m.round(pos * 3); + if (size < 8) size = 8; + that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px'; + } + pos = 0; + } else if (pos > that[dir + 'ScrollbarMaxScroll']) { + if (!that.options.fixedScrollbar) { + size = that[dir + 'ScrollbarIndicatorSize'] - m.round((pos - that[dir + 'ScrollbarMaxScroll']) * 3); + if (size < 8) size = 8; + that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px'; + pos = that[dir + 'ScrollbarMaxScroll'] + (that[dir + 'ScrollbarIndicatorSize'] - size); + } else { + pos = that[dir + 'ScrollbarMaxScroll']; + } + } + + that[dir + 'ScrollbarWrapper'].style[vendor + 'TransitionDelay'] = '0'; + that[dir + 'ScrollbarWrapper'].style.opacity = hidden && that.options.hideScrollbar ? '0' : '1'; + that[dir + 'ScrollbarIndicator'].style[vendor + 'Transform'] = trnOpen + (dir == 'h' ? pos + 'px,0' : '0,' + pos + 'px') + trnClose; + }, + + _start: function (e) { + var that = this, + point = hasTouch ? e.touches[0] : e, + matrix, x, y, + c1, c2; + + if (!that.enabled) return; + + if (that.options.onBeforeScrollStart) that.options.onBeforeScrollStart.call(that, e); + + if (that.options.useTransition || that.options.zoom) that._transitionTime(0); + + that.moved = false; + that.animating = false; + that.zoomed = false; + that.distX = 0; + that.distY = 0; + that.absDistX = 0; + that.absDistY = 0; + that.dirX = 0; + that.dirY = 0; + + // Gesture start + if (that.options.zoom && hasTouch && e.touches.length > 1) { + c1 = m.abs(e.touches[0].pageX-e.touches[1].pageX); + c2 = m.abs(e.touches[0].pageY-e.touches[1].pageY); + that.touchesDistStart = m.sqrt(c1 * c1 + c2 * c2); + + that.originX = m.abs(e.touches[0].pageX + e.touches[1].pageX - that.wrapperOffsetLeft * 2) / 2 - that.x; + that.originY = m.abs(e.touches[0].pageY + e.touches[1].pageY - that.wrapperOffsetTop * 2) / 2 - that.y; + + if (that.options.onZoomStart) that.options.onZoomStart.call(that, e); + } + + if (that.options.momentum) { + if (that.options.useTransform) { + // Very lame general purpose alternative to CSSMatrix + matrix = getComputedStyle(that.scroller, null)[vendor + 'Transform'].replace(/[^0-9-.,]/g, '').split(','); + x = matrix[4] * 1; + y = matrix[5] * 1; + } else { + x = getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, '') * 1; + y = getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, '') * 1; + } + + if (x != that.x || y != that.y) { + if (that.options.useTransition) that._unbind('webkitTransitionEnd'); + else cancelFrame(that.aniTime); + that.steps = []; + that._pos(x, y); + } + } + + that.absStartX = that.x; // Needed by snap threshold + that.absStartY = that.y; + + that.startX = that.x; + that.startY = that.y; + that.pointX = point.pageX; + that.pointY = point.pageY; + + that.startTime = e.timeStamp || (new Date()).getTime(); + + if (that.options.onScrollStart) that.options.onScrollStart.call(that, e); + + that._bind(MOVE_EV); + that._bind(END_EV); + that._bind(CANCEL_EV); + }, + + _move: function (e) { + var that = this, + point = hasTouch ? e.touches[0] : e, + deltaX = point.pageX - that.pointX, + deltaY = point.pageY - that.pointY, + newX = that.x + deltaX, + newY = that.y + deltaY, + c1, c2, scale, + timestamp = e.timeStamp || (new Date()).getTime(); + + if (that.options.onBeforeScrollMove) that.options.onBeforeScrollMove.call(that, e); + + // Zoom + if (that.options.zoom && hasTouch && e.touches.length > 1) { + c1 = m.abs(e.touches[0].pageX - e.touches[1].pageX); + c2 = m.abs(e.touches[0].pageY - e.touches[1].pageY); + that.touchesDist = m.sqrt(c1*c1+c2*c2); + + that.zoomed = true; + + scale = 1 / that.touchesDistStart * that.touchesDist * this.scale; + + if (scale < that.options.zoomMin) scale = 0.5 * that.options.zoomMin * Math.pow(2.0, scale / that.options.zoomMin); + else if (scale > that.options.zoomMax) scale = 2.0 * that.options.zoomMax * Math.pow(0.5, that.options.zoomMax / scale); + + that.lastScale = scale / this.scale; + + newX = this.originX - this.originX * that.lastScale + this.x, + newY = this.originY - this.originY * that.lastScale + this.y; + + this.scroller.style[vendor + 'Transform'] = trnOpen + newX + 'px,' + newY + 'px' + trnClose + ' scale(' + scale + ')'; + + if (that.options.onZoom) that.options.onZoom.call(that, e); + return; + } + + that.pointX = point.pageX; + that.pointY = point.pageY; + + // Slow down if outside of the boundaries + if (newX > 0 || newX < that.maxScrollX) { + newX = that.options.bounce ? that.x + (deltaX / 2) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX; + } + if (newY > that.minScrollY || newY < that.maxScrollY) { + newY = that.options.bounce ? that.y + (deltaY / 2) : newY >= that.minScrollY || that.maxScrollY >= 0 ? that.minScrollY : that.maxScrollY; + } + + if (that.absDistX < 6 && that.absDistY < 6) { + that.distX += deltaX; + that.distY += deltaY; + that.absDistX = m.abs(that.distX); + that.absDistY = m.abs(that.distY); + + return; + } + + // Lock direction + if (that.options.lockDirection) { + if (that.absDistX > that.absDistY + 5) { + newY = that.y; + deltaY = 0; + } else if (that.absDistY > that.absDistX + 5) { + newX = that.x; + deltaX = 0; + } + } + + that.moved = true; + that._pos(newX, newY); + that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; + that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; + + if (timestamp - that.startTime > 300) { + that.startTime = timestamp; + that.startX = that.x; + that.startY = that.y; + } + + if (that.options.onScrollMove) that.options.onScrollMove.call(that, e); + }, + + _end: function (e) { + if (hasTouch && e.touches.length != 0) return; + + var that = this, + point = hasTouch ? e.changedTouches[0] : e, + target, ev, + momentumX = { dist:0, time:0 }, + momentumY = { dist:0, time:0 }, + duration = (e.timeStamp || (new Date()).getTime()) - that.startTime, + newPosX = that.x, + newPosY = that.y, + distX, distY, + newDuration, + snap, + scale; + + that._unbind(MOVE_EV); + that._unbind(END_EV); + that._unbind(CANCEL_EV); + + if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e); + + if (that.zoomed) { + scale = that.scale * that.lastScale; + scale = Math.max(that.options.zoomMin, scale); + scale = Math.min(that.options.zoomMax, scale); + that.lastScale = scale / that.scale; + that.scale = scale; + + that.x = that.originX - that.originX * that.lastScale + that.x; + that.y = that.originY - that.originY * that.lastScale + that.y; + + that.scroller.style[vendor + 'TransitionDuration'] = '200ms'; + that.scroller.style[vendor + 'Transform'] = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + that.scale + ')'; + + that.zoomed = false; + that.refresh(); + + if (that.options.onZoomEnd) that.options.onZoomEnd.call(that, e); + return; + } + + if (!that.moved) { + if (hasTouch) { + if (that.doubleTapTimer && that.options.zoom) { + // Double tapped + clearTimeout(that.doubleTapTimer); + that.doubleTapTimer = null; + if (that.options.onZoomStart) that.options.onZoomStart.call(that, e); + that.zoom(that.pointX, that.pointY, that.scale == 1 ? that.options.doubleTapZoom : 1); + if (that.options.onZoomEnd) { + setTimeout(function() { + that.options.onZoomEnd.call(that, e); + }, 200); // 200 is default zoom duration + } + } else { + that.doubleTapTimer = setTimeout(function () { + that.doubleTapTimer = null; + + // Find the last touched element + target = point.target; + while (target.nodeType != 1) target = target.parentNode; + + if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') { + 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); + } + }, that.options.zoom ? 250 : 0); + } + } + + that._resetPos(200); + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + if (duration < 300 && that.options.momentum) { + momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX; + momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y - that.minScrollY : 0), that.options.bounce ? that.wrapperH : 0) : momentumY; + + newPosX = that.x + momentumX.dist; + newPosY = that.y + momentumY.dist; + + if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 }; + if ((that.y > that.minScrollY && newPosY > that.minScrollY) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 }; + } + + if (momentumX.dist || momentumY.dist) { + newDuration = m.max(m.max(momentumX.time, momentumY.time), 10); + + // Do we need to snap? + if (that.options.snap) { + distX = newPosX - that.absStartX; + distY = newPosY - that.absStartY; + if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); } + else { + snap = that._snap(newPosX, newPosY); + newPosX = snap.x; + newPosY = snap.y; + newDuration = m.max(snap.time, newDuration); + } + } + + that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration); + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + // Do we need to snap? + if (that.options.snap) { + distX = newPosX - that.absStartX; + distY = newPosY - that.absStartY; + if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) that.scrollTo(that.absStartX, that.absStartY, 200); + else { + snap = that._snap(that.x, that.y); + if (snap.x != that.x || snap.y != that.y) that.scrollTo(snap.x, snap.y, snap.time); + } + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + that._resetPos(200); + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + }, + + _resetPos: function (time) { + var that = this, + resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x, + resetY = that.y >= that.minScrollY || that.maxScrollY > 0 ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y; + + if (resetX == that.x && resetY == that.y) { + if (that.moved) { + that.moved = false; + if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end + } + + if (that.hScrollbar && that.options.hideScrollbar) { + if (vendor == 'webkit') that.hScrollbarWrapper.style[vendor + 'TransitionDelay'] = '300ms'; + that.hScrollbarWrapper.style.opacity = '0'; + } + if (that.vScrollbar && that.options.hideScrollbar) { + if (vendor == 'webkit') that.vScrollbarWrapper.style[vendor + 'TransitionDelay'] = '300ms'; + that.vScrollbarWrapper.style.opacity = '0'; + } + + return; + } + + that.scrollTo(resetX, resetY, time || 0); + }, + + _wheel: function (e) { + var that = this, + wheelDeltaX, wheelDeltaY, + deltaX, deltaY, + deltaScale; + + if ('wheelDeltaX' in e) { + wheelDeltaX = e.wheelDeltaX / 12; + wheelDeltaY = e.wheelDeltaY / 12; + } else if ('detail' in e) { + wheelDeltaX = wheelDeltaY = -e.detail * 3; + } else { + wheelDeltaX = wheelDeltaY = -e.wheelDelta; + } + + if (that.options.wheelAction == 'zoom') { + deltaScale = that.scale * Math.pow(2, 1/3 * (wheelDeltaY ? wheelDeltaY / Math.abs(wheelDeltaY) : 0)); + if (deltaScale < that.options.zoomMin) deltaScale = that.options.zoomMin; + if (deltaScale > that.options.zoomMax) deltaScale = that.options.zoomMax; + + if (deltaScale != that.scale) { + if (!that.wheelZoomCount && that.options.onZoomStart) that.options.onZoomStart.call(that, e); + that.wheelZoomCount++; + + that.zoom(e.pageX, e.pageY, deltaScale, 400); + + setTimeout(function() { + that.wheelZoomCount--; + if (!that.wheelZoomCount && that.options.onZoomEnd) that.options.onZoomEnd.call(that, e); + }, 400); + } + + return; + } + + deltaX = that.x + wheelDeltaX; + deltaY = that.y + wheelDeltaY; + + if (deltaX > 0) deltaX = 0; + else if (deltaX < that.maxScrollX) deltaX = that.maxScrollX; + + if (deltaY > that.minScrollY) deltaY = that.minScrollY; + else if (deltaY < that.maxScrollY) deltaY = that.maxScrollY; + + that.scrollTo(deltaX, deltaY, 0); + }, + + _mouseout: function (e) { + var t = e.relatedTarget; + + if (!t) { + this._end(e); + return; + } + + while (t = t.parentNode) if (t == this.wrapper) return; + + this._end(e); + }, + + _transitionEnd: function (e) { + var that = this; + + if (e.target != that.scroller) return; + + that._unbind('webkitTransitionEnd'); + + that._startAni(); + }, + + + /** + * + * Utilities + * + */ + _startAni: function () { + var that = this, + startX = that.x, startY = that.y, + startTime = (new Date).getTime(), + step, easeOut; + + if (that.animating) return; + + if (!that.steps.length) { + that._resetPos(400); + return; + } + + step = that.steps.shift(); + + if (step.x == startX && step.y == startY) step.time = 0; + + that.animating = true; + that.moved = true; + + if (that.options.useTransition) { + that._transitionTime(step.time); + that._pos(step.x, step.y); + that.animating = false; + if (step.time) that._bind('webkitTransitionEnd'); + else that._resetPos(0); + return; + } + + (function animate () { + var now = (new Date).getTime(), + newX, newY; + + if (now >= startTime + step.time) { + that._pos(step.x, step.y); + that.animating = false; + if (that.options.onAnimationEnd) that.options.onAnimationEnd.call(that); // Execute custom code on animation end + that._startAni(); + return; + } + + now = (now - startTime) / step.time - 1; + easeOut = m.sqrt(1 - now * now); + newX = (step.x - startX) * easeOut + startX; + newY = (step.y - startY) * easeOut + startY; + that._pos(newX, newY); + if (that.animating) that.aniTime = nextFrame(animate); + })(); + }, + + _transitionTime: function (time) { + time += 'ms'; + this.scroller.style[vendor + 'TransitionDuration'] = time; + if (this.hScrollbar) this.hScrollbarIndicator.style[vendor + 'TransitionDuration'] = time; + if (this.vScrollbar) this.vScrollbarIndicator.style[vendor + 'TransitionDuration'] = time; + }, + + _momentum: function (dist, time, maxDistUpper, maxDistLower, size) { + var deceleration = 0.0006, + speed = m.abs(dist) / time, + newDist = (speed * speed) / (2 * deceleration), + newTime = 0, outsideDist = 0; + + // Proportinally reduce speed if we are outside of the boundaries + if (dist > 0 && newDist > maxDistUpper) { + outsideDist = size / (6 / (newDist / speed * deceleration)); + maxDistUpper = maxDistUpper + outsideDist; + speed = speed * maxDistUpper / newDist; + newDist = maxDistUpper; + } else if (dist < 0 && newDist > maxDistLower) { + outsideDist = size / (6 / (newDist / speed * deceleration)); + maxDistLower = maxDistLower + outsideDist; + speed = speed * maxDistLower / newDist; + newDist = maxDistLower; + } + + newDist = newDist * (dist < 0 ? -1 : 1); + newTime = speed / deceleration; + + return { dist: newDist, time: m.round(newTime) }; + }, + + _offset: function (el) { + var left = -el.offsetLeft, + top = -el.offsetTop; + + while (el = el.offsetParent) { + left -= el.offsetLeft; + top -= el.offsetTop; + } + + if (el != this.wrapper) { + left *= this.scale; + top *= this.scale; + } + + return { left: left, top: top }; + }, + + _snap: function (x, y) { + var that = this, + i, l, + page, time, + sizeX, sizeY; + + // Check page X + page = that.pagesX.length - 1; + for (i=0, l=that.pagesX.length; i= that.pagesX[i]) { + page = i; + break; + } + } + if (page == that.currPageX && page > 0 && that.dirX < 0) page--; + x = that.pagesX[page]; + sizeX = m.abs(x - that.pagesX[that.currPageX]); + sizeX = sizeX ? m.abs(that.x - x) / sizeX * 500 : 0; + that.currPageX = page; + + // Check page Y + page = that.pagesY.length-1; + for (i=0; i= that.pagesY[i]) { + page = i; + break; + } + } + if (page == that.currPageY && page > 0 && that.dirY < 0) page--; + y = that.pagesY[page]; + sizeY = m.abs(y - that.pagesY[that.currPageY]); + sizeY = sizeY ? m.abs(that.y - y) / sizeY * 500 : 0; + that.currPageY = page; + + // Snap with constant speed (proportional duration) + time = m.round(m.max(sizeX, sizeY)) || 200; + + return { x: x, y: y, time: time }; + }, + + _bind: function (type, el, bubble) { + (el || this.scroller).addEventListener(type, this, !!bubble); + }, + + _unbind: function (type, el, bubble) { + (el || this.scroller).removeEventListener(type, this, !!bubble); + }, + + + /** + * + * Public methods + * + */ + destroy: function () { + var that = this; + + that.scroller.style[vendor + 'Transform'] = ''; + + // Remove the scrollbars + that.hScrollbar = false; + that.vScrollbar = false; + that._scrollbar('h'); + that._scrollbar('v'); + + // Remove the event listeners + that._unbind(RESIZE_EV, window); + that._unbind(START_EV); + that._unbind(MOVE_EV); + that._unbind(END_EV); + that._unbind(CANCEL_EV); + + if (that.options.hasTouch) { + that._unbind('mouseout', that.wrapper); + that._unbind(WHEEL_EV); + } + + if (that.options.useTransition) that._unbind('webkitTransitionEnd'); + + if (that.options.checkDOMChanges) clearInterval(that.checkDOMTime); + + if (that.options.onDestroy) that.options.onDestroy.call(that); + }, + + refresh: function () { + var that = this, + offset, + i, l, + els, + pos = 0, + page = 0; + + if (that.scale < that.options.zoomMin) that.scale = that.options.zoomMin; + that.wrapperW = that.wrapper.clientWidth || 1; + that.wrapperH = that.wrapper.clientHeight || 1; + + that.minScrollY = -that.options.topOffset || 0; + that.scrollerW = m.round(that.scroller.offsetWidth * that.scale); + that.scrollerH = m.round((that.scroller.offsetHeight + that.minScrollY) * that.scale); + that.maxScrollX = that.wrapperW - that.scrollerW; + that.maxScrollY = that.wrapperH - that.scrollerH + that.minScrollY; + that.dirX = 0; + that.dirY = 0; + + if (that.options.onRefresh) that.options.onRefresh.call(that); + + that.hScroll = that.options.hScroll && that.maxScrollX < 0; + that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH); + + that.hScrollbar = that.hScroll && that.options.hScrollbar; + that.vScrollbar = that.vScroll && that.options.vScrollbar && that.scrollerH > that.wrapperH; + + offset = that._offset(that.wrapper); + that.wrapperOffsetLeft = -offset.left; + that.wrapperOffsetTop = -offset.top; + + // Prepare snap + if (typeof that.options.snap == 'string') { + that.pagesX = []; + that.pagesY = []; + els = that.scroller.querySelectorAll(that.options.snap); + for (i=0, l=els.length; i= that.maxScrollX) { + that.pagesX[page] = pos; + pos = pos - that.wrapperW; + page++; + } + if (that.maxScrollX%that.wrapperW) that.pagesX[that.pagesX.length] = that.maxScrollX - that.pagesX[that.pagesX.length-1] + that.pagesX[that.pagesX.length-1]; + + pos = 0; + page = 0; + that.pagesY = []; + while (pos >= that.maxScrollY) { + that.pagesY[page] = pos; + pos = pos - that.wrapperH; + page++; + } + if (that.maxScrollY%that.wrapperH) that.pagesY[that.pagesY.length] = that.maxScrollY - that.pagesY[that.pagesY.length-1] + that.pagesY[that.pagesY.length-1]; + } + + // Prepare the scrollbars + that._scrollbar('h'); + that._scrollbar('v'); + + if (!that.zoomed) { + that.scroller.style[vendor + 'TransitionDuration'] = '0'; + that._resetPos(200); + } + }, + + scrollTo: function (x, y, time, relative) { + var that = this, + step = x, + i, l; + + that.stop(); + + if (!step.length) step = [{ x: x, y: y, time: time, relative: relative }]; + + for (i=0, l=step.length; i 0 ? 0 : pos.left < that.maxScrollX ? that.maxScrollX : pos.left; + pos.top = pos.top > that.minScrollY ? that.minScrollY : pos.top < that.maxScrollY ? that.maxScrollY : pos.top; + time = time === undefined ? m.max(m.abs(pos.left)*2, m.abs(pos.top)*2) : time; + + that.scrollTo(pos.left, pos.top, time); + }, + + scrollToPage: function (pageX, pageY, time) { + var that = this, x, y; + + if (that.options.snap) { + pageX = pageX == 'next' ? that.currPageX+1 : pageX == 'prev' ? that.currPageX-1 : pageX; + pageY = pageY == 'next' ? that.currPageY+1 : pageY == 'prev' ? that.currPageY-1 : pageY; + + pageX = pageX < 0 ? 0 : pageX > that.pagesX.length-1 ? that.pagesX.length-1 : pageX; + pageY = pageY < 0 ? 0 : pageY > that.pagesY.length-1 ? that.pagesY.length-1 : pageY; + + that.currPageX = pageX; + that.currPageY = pageY; + x = that.pagesX[pageX]; + y = that.pagesY[pageY]; + } else { + x = -that.wrapperW * pageX; + y = -that.wrapperH * pageY; + if (x < that.maxScrollX) x = that.maxScrollX; + if (y < that.maxScrollY) y = that.maxScrollY; + } + + that.scrollTo(x, y, time || 400); + }, + + disable: function () { + this.stop(); + this._resetPos(0); + this.enabled = false; + + // If disabled after touchstart we make sure that there are no left over events + this._unbind(MOVE_EV); + this._unbind(END_EV); + this._unbind(CANCEL_EV); + }, + + enable: function () { + this.enabled = true; + }, + + stop: function () { + if (this.options.useTransition) this._unbind('webkitTransitionEnd'); + else cancelFrame(this.aniTime); + this.steps = []; + this.moved = false; + this.animating = false; + }, + + zoom: function (x, y, scale, time) { + var that = this, + relScale = scale / that.scale; + + if (!that.options.useTransform) return; + + that.zoomed = true; + time = time === undefined ? 200 : time; + x = x - that.wrapperOffsetLeft - that.x; + y = y - that.wrapperOffsetTop - that.y; + that.x = x - x * relScale + that.x; + that.y = y - y * relScale + that.y; + + that.scale = scale; + that.refresh(); + + that.x = that.x > 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x; + that.y = that.y > that.minScrollY ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y; + + that.scroller.style[vendor + 'TransitionDuration'] = time + 'ms'; + that.scroller.style[vendor + 'Transform'] = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + scale + ')'; + that.zoomed = false; + }, + + isReady: function () { + return !this.moved && !this.zoomed && !this.animating; + } }; - // Is translate3d compatible? +//if (typeof exports !== 'undefined') exports.iScroll = iScroll; +//else window.iScroll = iScroll; - \ No newline at end of file +})(); \ No newline at end of file -- 2.39.2