X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F20_ui%2F15_ScrollBox.js;h=017fa70c9b776ccdc54758c9edf71622fddfccba;hb=a130cabd01fde8865990a59d849f5d5d08ed2119;hp=b1cec962530545fa01bda016f0272e92b9f72bd9;hpb=9e30a8480de03f0b36d2411a0ecb7f1d0f47b61f;p=pettanr%2FclientJs.git diff --git a/0.6.x/js/20_ui/15_ScrollBox.js b/0.6.x/js/20_ui/15_ScrollBox.js index b1cec96..017fa70 100644 --- a/0.6.x/js/20_ui/15_ScrollBox.js +++ b/0.6.x/js/20_ui/15_ScrollBox.js @@ -1,905 +1,44 @@ -(function(){ -var m = Math, - ABS = new Function( 'v', 'return v<0?-v:v' ); - - function Options(){}; - - X_Class_override( Options.prototype, { - hScroll : true, - vScroll : true, - x : 0, - y : 0, - bounce : true, - bounceLock : false, - momentum : true, - lockDirection : true, - useTransform : true, - useTransition : true, - topOffset : 0, - checkDOMChanges : false, // Experimental - handleClick : true, - - // Scrollbar - hScrollbar : true, - vScrollbar : true, - fixedScrollbar : X_UA.Android, - hideScrollbar : X_UA.iOS, - fadeScrollbar : X_UA.iOS, //&& has3d, - scrollbarClass : '', - - // Zoom - zoom : false, - zoomMin : 1, - zoomMax : 4, - doubleTapZoom : 2, - wheelAction : 'scroll', - - // Snap - snap : false, - snapThreshold : 1 //, - }); - - function Scrollbar( owner, dir ){ - this.owner = owner; - this.options = owner.options; - this.dir = dir; - if( dir === 'h' ){ - this.XorY = 'x'; - this.widthOrHeight = 'width'; - this.transrateXorY = 'translateX('; - }; +function X_UI_ScrollBox_momentum( current, start, time, lowerMargin, wrapperSize, deceleration ){ + var distance = current - start, + speed = Math.abs( distance ) / time, + destination, + duration; + + deceleration = deceleration === undefined ? 0.0006 : deceleration; + + destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); + duration = speed / deceleration; + + if( destination < lowerMargin ){ + destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin; + distance = Math.abs( destination - current ); + duration = distance / speed; + } else + if ( destination > 0 ) { + destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0; + distance = Math.abs( current ) + destination; + duration = distance / speed; }; - X_Class_override( Scrollbar.prototype, { - owner : null, - dir : null, - options : null, - XorY : 'y', - widthOrHeight : 'height', - transrateXorY : 'translateY(', - active : false, - xnodeWrapper : null, - xnodeIndicator : null, - wrapperSize : 0, - indicatorSize : 0, - maxScroll : 0, - scrollPercent : 0, - - update : function(){ - // remove scrollbar - if( !this.active ){ - if( this.xnodeWrapper ){ - X.CSS.transform && this.xnodeIndicator.css( 'transform', '' ); - this.xnodeWrapper.css( 'display', 'none' ); - }; - return; - }; - - // create scrollbar - if( !this.xnodeWrapper ){ - // Create the scrollbar wrapper - this.xnodeWrapper = this.owner.xnodeTarget.create( 'div' ) - .className( - this.options.scrollbarClass ? - this.options.scrollbarClass + this.dir.toUpperCase() : - this.dir === 'h' ? - ( this.vScrollbar && this.vScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-V-withH' : 'ScrollBox-Scrollbar-Wrapper-H' ) : - ( this.hScrollbar && this.hScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-H-withV' : 'ScrollBox-Scrollbar-Wrapper-V' ) - ); - - this.options.fadeScrollbar && - this.xnodeWrapper.css( - { - opacity : 0, - transitionProperty : 'opacity', - transitionDuration : '350ms' - } - ); - - // Create the scrollbar indicator - - this.xnodeIndicator = this.xnodeWrapper.create( 'div' ); - - !this.options.scrollbarClass && - this.xnodeIndicator.className( - this.dir === 'h' ? - 'ScrollBox-Scrollbar-Indicator-H' : - 'ScrollBox-Scrollbar-Indicator-V' - ); - //if (this.options.useTransition) bar.style.cssText += ';' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)'; - }; - - if( this.dir === 'h' ){ - this.wrapperSize = this.hScrollbarWrapper.clientWidth; - this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerW ), 8 ); - //this.hScrollbarIndicator.style.width = this.hScrollbarIndicatorSize + 'px'; - this.maxScroll = wrapperSize - indicatorSize; - this.scrollPercent = maxScroll / this.owner.maxScrollX; - } else { - this.wrapperSize = this.vScrollbarWrapper.clientHeight; - this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerH ), 8); - // this.vScrollbarIndicator.style.height = indicatorSize + 'px'; - this.maxScroll = this.vScrollbarSize - indicatorSize; - this.scrollPercent = maxScroll / this.owner.maxScrollY; - }; - this.xnodeIndicator.css( this.widthOrHeight, size + 'px' ); - // Reset position - this.updatePosition( this.owner[ this.XorY ], true ); - }, - - updatePosition : function( pos, hidden ){ - var size; - pos = this.scrollPercent * pos; - - if( pos < 0 ){ - if( !this.options.fixedScrollbar ){ - size = this.indicatorSize + m.round( pos * 3 ); - if( size < 8 ) size = 8; - //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px'; - this.xnodeIndicator.css( this.widthOrHeight, size + 'px' ); - }; - pos = 0; - } else - if( this.maxScroll < pos ){ - if( !this.options.fixedScrollbar ){ - size = this.indicatorSize - m.round( ( pos - this.maxScroll ) * 3 ); - if( size < 8 ) size = 8; - //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px'; - this.xnodeIndicator.css( this.widthOrHeight, size + 'px' ); - pos = this.maxScroll + this.indicatorSize - size; - } else { - pos = this.maxScroll; - }; - }; - - if (this.options.useTransition){ - this.xnodeWrapper.css( { - transitionDelay : '0', - opacity : hidden && this.options.hideScrollbar ? '0' : '1' - }); - //this.xnodeIndicator.style[transform] = 'translate(' + (dir == 'h' ? pos + 'px,0)' : '0,' + pos + 'px)') + translateZ; - this.xnodeIndicator.anime( this.dir === 'h' ? { x : pos } : { y : pos } ); - }; - } - }); - - // Constructor - function iScroll( uinodeRoot, uinodeTarget, xnodeTarget, xnodeScroller, options ){ - var i; - - this.uinodeRoot = uinodeRoot; - this.uinodeTarget = uinodeTarget; - this.xnodeTarget = xnodeTarget; - this.xnodeScroller = xnodeScroller; - - // Default options - this.options = new Options(); - - // User defined options - if( options ) for (i in options) X_EMPTY_OBJECT[ k ] || ( this.options[i] = options[i] ); - - this.options.hScroll && ( this.hScrollbar = new Scrollbar( 'h', this ) ); - this.options.vScroll && ( this.vScrollbar = new Scrollbar( 'v', this ) ); - - // Set starting position - this.x = this.options.x; - this.y = this.options.y; - - // Normalize options - this.options.useTransform = X.CSS.transform && this.options.useTransform; - this.options.hScrollbar = this.options.hScroll && this.options.hScrollbar; - this.options.vScrollbar = this.options.vScroll && this.options.vScrollbar; - this.options.zoom = this.options.useTransform && this.options.zoom; - this.options.useTransition = X.CSS.transition && this.options.useTransition; - - // Helpers FIX ANDROID BUG! - // translate3d and scale doesn't work together! - // Ignoring 3d ONLY WHEN YOU SET this.options.zoom - //if ( this.options.zoom && X_UA.isAndroid ){ - // translateZ = ''; - //} - - // Set some default styles - if (this.options.useTransform){ - this.scroller.style[X.CSS.transform] = 'translate(' + this.x + 'px,' + this.y + 'px)' + translateZ; - this.scroller.style[X.CSS.transformOrigin] = '0 0'; - } else { - this.scroller.style.cssText += ';position:absolute;top:' + this.y + 'px;left:' + this.x + 'px'; - }; - - if (this.options.useTransition){ - this.scroller.style[X.CSS.transition.Property] = this.options.useTransform ? X.CSS.cssVendor + 'transform' : 'top left'; - this.scroller.style[X.CSS.transition.Duration] = '0'; - this.scroller.style[X.CSS.transition.TimingFunction] = 'cubic-bezier(0.33,0.66,0.66,1)'; - this.options.fixedScrollbar = true; - }; - - this.refresh(); - - //this._bind(RESIZE_EV, window); - X.Dom.Event.add( window, RESIZE_EV, this ); - //this._bind(START_EV); - uinodeTarget.listen( X.UI.Event.DRAG_START, this ); + return { + destination : Math.round( destination ), + duration : duration + }; +}; + +var X_UI_ScrollBox_SUPPORT_ATTRS = { + // スクロール開始するために必要な移動距離、縦か横、どちらか制限する場合、より重要 + directionLockThreshold : [ 10, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.LENGTH ], + scrollX : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ], + scrollY : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ], + enabled : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ], + bounce : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ], + bounceTime : [ 600, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.TIME ], + useWheel : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ], + useKey : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ], }; - -// Prototype -X_Class_override( iScroll.prototype, { - uinodeRoot : null, - uinodeTarget : null, - xnodeTarget : null, - xnodeScroller : null, - options : null, - enabled : true, - x : 0, - y : 0, - steps : [], - scale : 1, - currPageX : 0, - currPageY : 0, - pagesX : [], - pagesY : [], - animeTimerID : 0, - wheelZoomCount : 0, - - wrapperW : 0, - wrapperH : 0, - minScrollY : 0, - scrollerW : 0, - scrollerH : 0, - maxScrollX : 0, - maxScrollY : 0, - dirX : 0, - dirY : 0, - hScrollbar : false, - vScrollbar : false, - wrapperOffsetLeft : 0, - wrapperOffsetTop : 0, - - - currPageX : 0, - currPageY : 0, - - moved : false, - animating : false, - zoomed : false, - distX : false, - distY : false, - absDistX : false, - absDistY : false, - absStartX : false, // Needed by snap threshold - absStartY : false, - startX : false, - startY : false, - pointX : false, - pointY : false, - startTime : false, - - handleEvent: function (e) { - switch(e.type) { - case X.UI.Event.DRAG : - return this._move(e); - case X.UI.Event.WHEEL : - return this._wheel(e); - case X.UI.Event.DRAG_START : - //if (!hasTouch && e.button !== 0) return; - return this._start(e); - case X.UI.Event.DRAG_END : - return this._end(e); - case X.UI.Event.ANIME_END : - return this._transitionEnd(e); - case RESIZE_EV : - return this._resize(); - } - }, - - _trigger : function( type, e ){ - - return this.uinodeTarget.dispatch( ); - }, - - _resize: function () { - X.Timer.once( X_UA.Android ? 200 : 0, this, this.refresh ); - // setTimeout( this.refresh(), isAndroid ? 200 : 0); - }, - - _updateScrollPosition: function( x, y, time ){ - if (this.zoomed) return; - - this.xnodeScroller.anime({ - x : this.x = this.hScrollbar && this.hScrollbar.active ? m.round(x) : 0, - y : this.y = this.vScrollbar && this.vScrollbar.active ? m.round(y) : 0 - }, time ); - - this.hScrollbar && this.hScrollbar.active && this.hScrollbar.updatePosition( this.x ); - this.vScrollbar && this.vScrollbar.active && this.vScrollbar.updatePosition( this.y ); - }, - - _start: function (e) { - var point = hasTouch ? e.touches[0] : e, - matrix, x, y, - ret; - //c1, c2; - - if (!this.enabled) return; - - //if (this.options.onBeforeScrollStart) this.options.onBeforeScrollStart.call(this, e); - if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_START ) ) & X.Callback.PREVENT_DEFAULT ){ - return ret; - }; - - //if (this.options.useTransition || this.options.zoom) this._transitionTime(0); - - this.moved = false; - this.animating = false; - this.zoomed = false; - this.distX = 0; - this.distY = 0; - this.absDistX = 0; - this.absDistY = 0; - this.dirX = 0; - this.dirY = 0; - - if (this.options.momentum) { - if (this.options.useTransform) { - // Very lame general purpose alternative to CSSMatrix - matrix = getComputedStyle(this.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(','); - x = +(matrix[12] || matrix[4]); - y = +(matrix[13] || matrix[5]); - } else { - x = +getComputedStyle(this.scroller, null).left.replace(/[^0-9-]/g, ''); - y = +getComputedStyle(this.scroller, null).top.replace(/[^0-9-]/g, ''); - }; - - if (x !== this.x || y !== this.y) { - if (this.options.useTransition){ - //this._unbind(TRNEND_EV); - X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - } else { - X.Timer.cancelFrame(this.animeTimerID); - }; - this.steps = this.steps ? ( this.steps.length = 0 ) : []; - this._updateScrollPosition( x, y, 0 ); - //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this); - return this._trigger( X.UI.Event.SCROLL_END, e ); - }; - }; - - this.absStartX = this.x; // Needed by snap threshold - this.absStartY = this.y; - - this.startX = this.x; - this.startY = this.y; - this.pointX = point.pageX; - this.pointY = point.pageY; - - this.startTime = e.timeStamp || X.Timer.now(); - - this.uinodeRoot.listen( X.UI.Event.DRAG, this ); - this.uinodeRoot.listen( X.UI.Event.DRAG_END, this ); - - return this._trigger( X.UI.Event.SCROLL_START, e ); - }, - - _move: function (e) { - var point = hasTouch ? e.touches[0] : e, - deltaX = point.pageX - this.pointX, - deltaY = point.pageY - this.pointY, - newX = this.x + deltaX, - newY = this.y + deltaY, - c1, c2, scale, - timestamp = e.timeStamp ||X.Timer.now(), ret; - - //if (this.options.onBeforeScrollMove) this.options.onBeforeScrollMove.call(this, e); - if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_MOVE ) ) & X.Callback.PREVENT_DEFAULT ){ - return ret; - }; - - this.pointX = point.pageX; - this.pointY = point.pageY; - - // Slow down if outside of the boundaries - if (newX > 0 || newX < this.maxScrollX) { - newX = this.options.bounce ? this.x + (deltaX / 2) : newX >= 0 || this.maxScrollX >= 0 ? 0 : this.maxScrollX; - }; - if (newY > this.minScrollY || newY < this.maxScrollY) { - newY = this.options.bounce ? this.y + (deltaY / 2) : newY >= this.minScrollY || this.maxScrollY >= 0 ? this.minScrollY : this.maxScrollY; - }; - - this.distX += deltaX; - this.distY += deltaY; - this.absDistX = ABS(this.distX); - this.absDistY = ABS(this.distY); - - if (this.absDistX < 6 && this.absDistY < 6) { - return; - }; - - // Lock direction - if (this.options.lockDirection) { - if (this.absDistX > this.absDistY + 5) { - newY = this.y; - deltaY = 0; - } else if (this.absDistY > this.absDistX + 5) { - newX = this.x; - deltaX = 0; - }; - }; - - this.moved = true; - this._updateScrollPosition(newX, newY, 0); - this.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; - this.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; - - if (timestamp - this.startTime > 300) { - this.startTime = timestamp; - this.startX = this.x; - this.startY = this.y; - }; - - //if (this.options.onScrollMove) this.options.onScrollMove.call(this, e); - return this._trigger( X.UI.Event.SCROLL_MOVE, e ); - }, - - _end: function (e) { - if (hasTouch && e.touches.length !== 0) return; - - var point = hasTouch ? e.changedTouches[0] : e, - momentumX = { dist:0, time:0 }, - momentumY = { dist:0, time:0 }, - duration = ( e.timeStamp ||X.Timer.now() ) - this.startTime, - newPosX = this.x, - newPosY = this.y, - distX, distY, - newDuration, - snap, - scale; - - this.uinodeRoot.unlisten( X.UI.Event.DRAG, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this ); - - //this._unbind(MOVE_EV, window); - //this._unbind(END_EV, window); - //this._unbind(CANCEL_EV, window); - - //if (this.options.onBeforeScrollEnd) this.options.onBeforeScrollEnd.call(this, e); - - - if (!this.moved) { - - - this._resetPos(400); - - //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e); - return; - }; - - if (duration < 300 && this.options.momentum) { - momentumX = newPosX ? this._momentum(newPosX - this.startX, duration, -this.x, this.scrollerW - this.wrapperW + this.x, this.options.bounce ? this.wrapperW : 0) : momentumX; - momentumY = newPosY ? this._momentum(newPosY - this.startY, duration, -this.y, (this.maxScrollY < 0 ? this.scrollerH - this.wrapperH + this.y - this.minScrollY : 0), this.options.bounce ? this.wrapperH : 0) : momentumY; - - newPosX = this.x + momentumX.dist; - newPosY = this.y + momentumY.dist; - - if ((this.x > 0 && newPosX > 0) || (this.x < this.maxScrollX && newPosX < this.maxScrollX)) momentumX = { dist:0, time:0 }; - if ((this.y > this.minScrollY && newPosY > this.minScrollY) || (this.y < this.maxScrollY && newPosY < this.maxScrollY)) momentumY = { dist:0, time:0 }; - - if (momentumX.dist || momentumY.dist) { - newDuration = m.max(m.max(momentumX.time, momentumY.time), 10); - - this.scrollTo(m.round(newPosX), m.round(newPosY), newDuration); - - //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e); - return; - }; - }; - - this._resetPos(200); - //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e); - }, - - /* - _onZoomEndEvent : null, - _onZoomEndTimerComplete : function(){ - this.options.onZoomEnd.call( this, this._onZoomEndEvent ); - }, - */ - - /* - _onDobleTapTimerPoint : null, - _onDobleTapTimerComplete : function () { - var point = this._onDobleTapTimerPoint, - target, ev; - this.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); - } - },*/ - - _resetPos: function (time) { - var resetX = - 0 <= this.x ? - 0 : - this.x < this.maxScrollX ? - this.maxScrollX : - this.x, - resetY = - this.minScrollY <= this.y || 0 < this.maxScrollY ? - this.minScrollY : - this.y < this.maxScrollY ? - this.maxScrollY : - this.y; - - if( resetX === this.x && resetY === this.y ){ - if( this.moved ){ - this.moved = false; - this._trigger( X.UI.Event.SCROLL_END ); - //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this); // Execute custom code on scroll end - }; - if( this.options.hideScrollbar ){ - this.hScrollbar && this.hScrollbar.active && - - //if (vendor == 'webkit') this.hScrollbarWrapper.style[transitionDelay] = '300ms'; - //this.hScrollbarWrapper.style.opacity = '0'; - this.hScrollbar.xnodeWraper.anime( { - opacity : 0 - }, 300 ); - this.vScrollbar && this.vScrollbar.active && - //if (vendor == 'webkit') this.vScrollbarWrapper.style[transitionDelay] = '300ms'; - //this.vScrollbarWrapper.style.opacity = '0'; - this.hScrollbar.xnodeWraper.anime( { - opacity : 0 - }, 300 ); - }; - return; - }; - this.scrollTo(resetX, resetY, time || 0); - }, - - _wheel: function (e) { - var wheelDeltaX, wheelDeltaY, - deltaX, deltaY, - deltaScale; -/* - if ('wheelDeltaX' in e) { - wheelDeltaX = e.wheelDeltaX / 12; - wheelDeltaY = e.wheelDeltaY / 12; - } else if('wheelDelta' in e) { - wheelDeltaX = wheelDeltaY = e.wheelDelta / 12; - } else if ('detail' in e) { - wheelDeltaX = wheelDeltaY = -e.detail * 3; - } else { - return; - } */ - - deltaX = this.x + e.wheelDeltaX; - deltaY = this.y + e.wheelDeltaY; - - deltaX = - 0 < deltaX ? - 0 : - deltaX < this.maxScrollX ? - this.maxScrollX : deltaX; - - deltaY = - this.minScrollY < deltaY ? - this.minScrollY : - deltaY < this.maxScrollY ? - this.maxScrollY : deltaY; - - this.maxScrollY < 0 && this.scrollTo(deltaX, deltaY, 0); - }, - - /* - _wheelTimerCompleteEvent : null, - _wheelTimerComplete : function() { - this.wheelZoomCount--; - if (!this.wheelZoomCount && this.options.onZoomEnd) this.options.onZoomEnd.call(this, this._wheelTimerCompleteEvent ); - }, - */ - - _transitionEnd: function( e ){ - if( e.target !== this.xnodeScroller ) return; - - //this._unbind(TRNEND_EV); - //X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - this.animating = false; - - this._startAnime(); - - return X.Callback.UN_LISTEN; - }, - - - /** - * - * Utilities - * - */ - _startAnime: function () { - var startX = this.x, - startY = this.y, - step, animate; - - if (this.animating) return; - - if (!this.steps.length) { - this._resetPos(400); - return; - }; - - step = this.steps.shift(); - - if( step.x === startX && step.y === startY ) step.time = 0; - - this.animating = true; - this.moved = true; - - //if (this.options.useTransition) { - //this._transitionTime(step.time); - this._updateScrollPosition( step.x, step.y, step.time ); - //this.animating = false; - this.xnodeScroller.listenOnce( X.UI.Event.ANIME_END, this ); - //step.time ? X.Dom.Event.add( this.scroller, TRNEND_EV, this ) /* this._bind(TRNEND_EV) */ : this._resetPos(0); - //return; - //} - //this._doAnimate( X.Timer.now(), step, startX, startY ); - }, - -/* - _doAnimate : function( startTime, step, startX, startY ){ - var now =X.Timer.now(), - easeOut, newX, newY; - - if (now >= startTime + step.time) { - this._updateScrollPosition(step.x, step.y); - this.animating = false; - //if (this.options.onAnimationEnd) this.options.onAnimationEnd.call( this ); // Execute custom code on animation end - this._startAnime(); - 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; - this._updateScrollPosition(newX, newY); - if( this.animating ) this.animeTimerID = X.Timer.requestFrame( this, this._doAnimate, [ startTime, step, startX, startY ] ); - }, -*/ - - _momentum: function (dist, time, maxDistUpper, maxDistLower, size) { - var deceleration = 0.0006, - speed = 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 }; - }, - - /* - _bind: function (type, el, bubble) { - X.Dom.Event.add( el || this.scroller, type, this ); - }, - - _unbind: function (type, el, bubble) { - X.Dom.Event.remove( el || this.scroller, type, this ); - }, - */ - - /** - * - * Public methods - * - */ - destroy: function () { - this.scroller.style[transform] = ''; - - // Remove the scrollbars - this.hScrollbar && this.hScrollbar.destroy(); - this.vScrollbar && this.vScrollbar.destroy(); - - // Remove the event listeners - X.Dom.Event.add( window, RESIZE_EV, this ); - this.uinodeTarget.unlisten( X.UI.Event.DRAG_START, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this ); - - //this._unbind(RESIZE_EV, window); - //this._unbind(START_EV); - //this._unbind(MOVE_EV, window); - //this._unbind(END_EV, window); - //this._unbind(CANCEL_EV, window); - - if (!this.options.hasTouch) { - //this._unbind('wheel'); - } - - // if (this.options.useTransition) this._unbind(TRNEND_EV); - this.options.useTransition && X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - - //if (this.options.checkDOMChanges) clearInterval(this.checkDOMTime); - - //if (this.options.onDestroy) this.options.onDestroy.call(this); - }, - - refresh: function () { - var offset, - i, l, - els, - pos = 0, - page = 0; - - if (this.scale < this.options.zoomMin) this.scale = this.options.zoomMin; - this.wrapperW = this.wrapper.clientWidth || 1; - this.wrapperH = this.wrapper.clientHeight || 1; - - this.minScrollY = -this.options.topOffset || 0; - this.scrollerW = m.round(this.scroller.offsetWidth * this.scale); - this.scrollerH = m.round((this.scroller.offsetHeight + this.minScrollY) * this.scale); - this.maxScrollX = this.wrapperW - this.scrollerW; - this.maxScrollY = this.wrapperH - this.scrollerH + this.minScrollY; - this.dirX = 0; - this.dirY = 0; - - // if (this.options.onRefresh) this.options.onRefresh.call(this); - this._trigger( X.UI.Event.SCROLL_REFRESH, {} ); - - this.hScrollbar && ( this.hScrollbar.active = this.maxScrollX < 0 ); - this.vScrollbar && ( this.vScrollbar.active = !this.options.bounceLock && !this.hScroll || this.scrollerH > this.wrapperH ); - - offset = this._offset(this.wrapper); - this.wrapperOffsetLeft = -offset.left; - this.wrapperOffsetTop = -offset.top; - - // Prepare snap - if (typeof this.options.snap == 'string') { - this.pagesX = []; - this.pagesY = []; - els = this.scroller.querySelectorAll(this.options.snap); - for (i=0, l=els.length; i= this.maxScrollX) { - this.pagesX[page] = pos; - pos = pos - this.wrapperW; - page++; - } - if (this.maxScrollX%this.wrapperW) this.pagesX[this.pagesX.length] = this.maxScrollX - this.pagesX[this.pagesX.length-1] + this.pagesX[this.pagesX.length-1]; - - pos = 0; - page = 0; - this.pagesY = []; - while (pos >= this.maxScrollY) { - this.pagesY[page] = pos; - pos = pos - this.wrapperH; - page++; - } - if (this.maxScrollY%this.wrapperH) this.pagesY[this.pagesY.length] = this.maxScrollY - this.pagesY[this.pagesY.length-1] + this.pagesY[this.pagesY.length-1]; - } - - // Prepare the scrollbars - this._scrollbar('h'); - this._scrollbar('v'); - - if (!this.zoomed) { - this.scroller.style[transitionDuration] = '0'; - this._resetPos(400); - } - }, - - scrollTo: function (x, y, time, relative) { - var step = x, - i, l; - - this.stop(); - - if( !step.length ) step = [{ x: x, y: y, time: time, relative: relative }]; - - for( i = 0, l = step.length; i < l; ++i ){ - if( step[ i ].relative ){ - step[ i ].x = this.x - step[ i ].x; - step[ i ].y = this.y - step[ i ].y; - }; - this.steps.push( { - x : step[i].x, - y : step[i].y, - time : step[i].time || 0 - }); - }; - - this._startAnime(); - }, - - 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, window); - //this._unbind(END_EV, window); - //this._unbind(CANCEL_EV, window); - this.uinodeRoot.unlisten( X.UI.Event.DRAG, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this ); - }, - - enable: function () { - this.enabled = true; - }, - - stop: function () { - //if (this.options.useTransition) this._unbind(TRNEND_EV); - //else X.Timer.cancelFrame( this.animeTimerID ); - /* - if (this.options.useTransition){ - X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - } else { - X.Timer.cancelFrame(this.animeTimerID); - }; - */ - this.xnodeScroller.stop(); - if( this.steps ) this.steps.length = 0; - this.moved = false; - this.animating = false; - }, - - isReady: function () { - return !this.moved && !this.zoomed && !this.animating; - } -} ); - X.UI._ScrollBox = X.UI._ChromeBox.inherits( @@ -910,40 +49,122 @@ X.UI._ScrollBox = X.UI._ChromeBox.inherits( //elmScroller : null, //elmScrollbar : null, + supportAttrs : X.UI.Attr.createAttrDef( X.UI.Attr.Support, X_UI_ScrollBox_SUPPORT_ATTRS ), + scrolling : false, - _scrollX : 0, - _scrollY : 0, - scrollXPercent : 0, - scrollYPercent : 0, + + initiated : '', + moved : false, + distX : 0, + distY : 0, + directionX : 0, + directionY : 0, + directionLocked : '', + startTime : 0, + endTime : 0, + isAnimating : false, + startX : 0, + startY : 0, + absStartX : 0, + absStartY : 0, + pointX : 0, + pointY : 0, + maxScrollX : 0, + maxScrollY : 0, + hasHScroll : false, + hasVScroll : false, + + wrapperOffset : 0, + wheelTimeout : 0, + requestFrameID : 0, + + _scrollX : 0, + _scrollY : 0, + scrollXPercent : 0, + scrollYPercent : 0, + + lastScrollWidth : 0, + lastScrollHeight : 0, + lastBoxWidth : 0, + lastBoxHeight : 0, _containerNode : null, scrollManager : null, Constructor : function( layout, args ){ this.Super( layout, args ); - this._containerNode = _X_Class_getPrivate( this.containerNode ); + this._containerNode = X_Class_getPrivate( this.containerNode ); }, creationComplete : function(){ - X.UI._AbstractUINode.prototype.creationComplete.call( this, arguments ); - this.scrollManager = new iScroll( this.root, this.User, this.rawElement, this._containerNode.rawElement ); + X.UI._Box.prototype.creationComplete.apply( this, arguments ); + this.scrollManager; this._check(); }, calculate : function(){ - X.UI._AbstractUINode.prototype.calculate.call( this, arguments ); - this._check(); + this.lastScrollWidth = this.scrollWidth; + this.lastScrollHeight = this.scrollHeight; + this.lastBoxWidth = this.boxWidth; + this.lastBoxHeight = this.boxHeight; + + X.UI._Box.prototype.calculate.apply( this, arguments ); + + if( + this.lastScrollWidth !== this.scrollWidth || this.lastScrollHeight !== this.scrollHeight || + this.lastBoxWidth !== this.boxWidth || this.lastBoxHeight !== this.boxHeight + ){ + // scroll の停止、GPU の解除 + this._check(); + }; + + }, + + scrollBy : function( x, y, opt_time, opt_easing ){ + this.scrollTo( this.x + x, this.y + y, opt_time, opt_easing ); + }, + + scrollTo : function( x, y, opt_time, opt_easing ){ + opt_time = 0 <= opt_time ? opt_time : 0; + opt_easing = opt_easing || 'circular'; + + this.isInTransition = !!opt_time; + + this.containerNode.animate( + { + x : this.x, + y : this.y + }, + { + x : x, + y : y + }, + opt_time, opt_easing, 1000 + ); + + this.x = x; + this.y = y; + + if( this.indicators ){ + for( i = this.indicators.length; i--; ){ + this.indicators[ i ].updatePosition( opt_time, opt_easing ); + }; + }; }, _check : function(){ - if( this.w < this._containerNode.w || this.h < this._containerNode.h ){ + var needVScroll, needHScroll; + if( this.boxWidth < this._containerNode.scrollWidth || this.boxHeight < this._containerNode.scrollHeight ){ // scroll if( this.scrolling ){ // fix scroll position from scrollXPercent, scrollYPercent + // } else { // create scroller - this.listen( X.UI.Event.POINTER_START, this ); + + + this.listen( X.UI.Event.POINTER_START, X_UI_ScrollBox_onStart ); this._move( 0, 0 ); @@ -954,6 +175,7 @@ X.UI._ScrollBox = X.UI._ChromeBox.inherits( // no scroll if( this.scrolling ){ // remove scroller + this.unlisten( X.UI.Event.POINTER_START ); ( this._scrollX !== 0 || this._scrollY !== 0 ) && this._move( 0, 0 ); @@ -961,30 +183,12 @@ X.UI._ScrollBox = X.UI._ChromeBox.inherits( }; }, - handleEvent : function( e ){ - switch( e.type ){ - case X.UI.Event.POINTER_START : - this.listen( X.UI.Event.POINTER_MOVE, this ); - this.listen( X.UI.Event.POINTER_END, this ); - - break; - case X.UI.Event.POINTER_MOVE : - - break; - case X.UI.Event.POINTER_END : - this.unlisten( X.UI.Event.POINTER_MOVE, this ); - this.unlisten( X.UI.Event.POINTER_END, this ); - - break; - }; - }, - _move : function( x, y ){ }, _remove : function(){ - X.UI._AbstractUINode.prototype._remove.call( this, arguments ); + X.UI._AbstractUINode.prototype._remove.apply( this, arguments ); if( this.scrolling ){ // remove scroll }; @@ -993,7 +197,285 @@ X.UI._ScrollBox = X.UI._ChromeBox.inherits( } ); -})(); + +function X_UI_ScrollBox_onStart( e ){ + var ret = X.Callback.NONE; + + // React to left mouse button only + if( e.pointerType === 'mouse' && e.button !== 0 ){ + return ret; + }; + + if( !this.enabled || ( this.initiated && e.pointerType !== this.initiated ) ){ + return ret; + }; + + this.initiated = e.pointerType; + this.moved = false; + this.distX = 0; + this.distY = 0; + this.directionX = 0; + this.directionY = 0; + this.directionLocked = ''; + this.startTime = X_Timer_now(); + + // スクロール中の停止 + if( this.isAnimating ){ + delete this.isAnimating; + this.dispatch( X.UI.Event.SCROLL_END ); + }; + + this.startX = this.x; + this.startY = this.y; + this.absStartX = this.x; + this.absStartY = this.y; + this.pointX = e.pageX; + this.pointY = e.pageY; + + this.listen( X.UI.Event.POINTER_MOVE, X_UI_ScrollBox_onMove ); + this.listen( X.UI.Event.POINTER_END , X_UI_ScrollBox_onEnd ); + + //console.log( 'start : 3' ); + return ret | X.Callback.PREVENT_DEFAULT; +}; + +function X_UI_ScrollBox_onMove( e ){ + var ret = X.Callback.NONE, + deltaX, deltaY, timestamp, + newX, newY, + absDistX, absDistY; + // 規定以上の move でスクロール開始 + + if( !this.enabled || e.pointerType !== this.initiated ){ + return ret; + }; + + // gpu の用意 + if( !this.containerNode._anime ){ + console.log( 'gpuレイヤーの用意' ); + this._translate( this.x, this.y ); + return ret; + }; + + deltaX = e.pageX - this.pointX; + deltaY = e.pageY - this.pointY; + timestamp = X_Timer_now(); + + this.pointX = e.pageX; + this.pointY = e.pageY; + + this.distX += deltaX; + this.distY += deltaY; + absDistX = Math.abs(this.distX); + absDistY = Math.abs(this.distY); + + // We need to move at least 10 pixels for the scrolling to initiate + if( 300 < timestamp - this.endTime && ( absDistX < 10 && absDistY < 10 ) ){ + return ret; + }; + + // If you are scrolling in one direction lock the other + if( !this.directionLocked ){ + if( absDistX > absDistY + this.directionLockThreshold ){ + this.directionLocked = 'h'; // lock horizontally + } else + if( absDistY >= absDistX + this.directionLockThreshold ){ + this.directionLocked = 'v'; // lock vertically + } else { + this.directionLocked = 'n'; // no lock + }; + }; + + if( this.directionLocked === 'h' ){ + deltaY = 0; + } else + if( this.directionLocked === 'v' ){ + deltaX = 0; + }; + + deltaX = this.hasHScroll ? deltaX : 0; + deltaY = this.hasVScroll ? deltaY : 0; + + if( !this.moved ){ + this.dispatch( X.UI.Event.SCROLL_BEFORE_MOVE ); + this.moved = true; + this.minusX = deltaX; + this.minusY = deltaY; + } else { + this.dispatch( X.UI.Event.SCROLL_MOVE ); + }; + + newX = this.x + deltaX;// - this.minusX; + newY = this.y + deltaY;// - this.minusY; + + // Slow down if outside of the boundaries + if( 0 < newX || newX < this.maxScrollX ){ + newX = this.bounce ? this.x + ( deltaX ) / 3 : 0 < newX ? 0 : this.maxScrollX; + }; + if( 0 < newY || newY < this.maxScrollY ){ + newY = this.bounce ? this.y + ( deltaY ) / 3 : 0 < newY ? 0 : this.maxScrollY; + }; + + this.directionX = 0 < deltaX ? -1 : deltaX < 0 ? 1 : 0; + this.directionY = 0 < deltaY ? -1 : deltaY < 0 ? 1 : 0; + + this._translate( newX, newY ); + + if( 300 < timestamp - this.startTime ){ + this.startTime = timestamp; + this.startX = this.x; + this.startY = this.y; + }; + // イベントの拘束 + return ret | X.Callback.PREVENT_DEFAULT | X.Callback.MONOPOLY; +}; + +function X_UI_ScrollBox_onEnd( e ){ + var ret = X.Callback.NONE, + time = 0, + easing = '', + newX, newY, + momentumX, momentumY, + duration, distanceX, distanceY; + + this.unlisten( X.UI.Event.POINTER_MOVE, X_UI_ScrollBox_onMove ); + this.unlisten( X.UI.Event.POINTER_END, X_UI_ScrollBox_onEnd ); + + if( !this.enabled || e.pointerType !== this.initiated ){ + return ret; + }; + + delete this.isInTransition; + delete this.initiated; + this.endTime = X_Timer_now(); + + duration = this.endTime - this.startTime; + newX = Math.round( this.x ); + newY = Math.round( this.y ); + distanceX = Math.abs(newX - this.startX); + distanceY = Math.abs(newY - this.startY); + + // reset if we are outside of the boundaries + if( X_UI_ScrollBox_resetPosition( this, this.options.bounceTime ) ){ + return ret; + }; + + // we scrolled less than 10 pixels + if( !this.moved ){ + // this.dispatch( X_Event.CANCELED ); + return ret; + }; + + this.scrollTo( newX, newY, 0 ); // ensures that the last position is rounded + + // start momentum animation if needed + if( this.options.momentum && duration < 300 ){ + momentumX = this.hasHScroll ? + X_UI_ScrollBox_momentum( this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration ) :{ destination: newX, duration: 0 }; + momentumY = this.hasVScroll ? + X_UI_ScrollBox_momentum( this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration ) : { destination: newY, duration: 0 }; + newX = momentumX.destination; + newY = momentumY.destination; + time = Math.max( momentumX.duration, momentumY.duration ); + this.isInTransition = 1; + }; + + if( newX != this.x || newY != this.y ){ + // change easing function when scroller goes out of the boundaries + if( 0 < newX || newX < this.maxScrollX || 0 < newY || newY < this.maxScrollY ){ + easing = 'quadratic'; + }; + + this.scrollTo( newX, newY, time, easing ); + return ret; + }; + + this.dispatch( X.UI.Event.SCROLL_END ); + + return ret; +}; + +function X_UI_ScrollBox_resetPosition( that, time ){ + var x = this.x, + y = this.y; + + time = time || 0; + + if( !this.hasHScroll || 0 < this.x ){ + x = 0; + } else + if( this.x < this.maxScrollX ){ + x = this.maxScrollX; + }; + + if( !this.hasVScroll || 0 < this.y ){ + y = 0; + } else + if( this.y < this.maxScrollY ){ + y = this.maxScrollY; + }; + + if( x === this.x && y === this.y ){ + console.log( 'no バウンド' ); + return false; + }; + + console.log( 'バウンド!' ); + this.scrollTo( x, y, time, this.options.bounceEasing ); + + return true; +}; + +function X_UI_ScrollBox_translate( x, y ){ + this.containerNode.animate( + { + x : this.x, + y : this.y + }, + { + x : x, + y : y + }, + 0, '', 300 + ); + + this.x = x; + this.y = y; + + if( this.indicators ){ + for( i = this.indicators.length; i--; ){ + this.indicators[ i ].updatePosition(); + }; + }; +}; + +function X_UI_ScrollBox_refresh( remove ){ + this.maxScrollX = this.boxWidth - this.containerNode.boxWidth; + this.maxScrollY = this.boxHeight - this.containerNode.boxHeight; + + this.hasHScroll = this.User.attr( 'scrollX' ) && this.maxScrollX < 0; + this.hasVScroll = this.User.attr( 'scrollY' ) && this.maxScrollY < 0; + + if( !this.hasHScroll ){ + this.maxScrollX = 0; + this.scrollerWidth = this.wrapperWidth; + }; + + if( !this.hasVScroll ){ + this.maxScrollY = 0; + this.scrollerHeight = this.wrapperHeight; + }; + + delete this.endTime; + delete this.directionX; + delete this.directionY; + + this.wrapperOffset = this.xnodeWrapper.offset(); + + //this.dispatch('refresh'); + + X_UI_ScrollBox_resetPosition( this, 0 ); +}; X.UI.ScrollBox = X.UI.ChromeBox.inherits( 'ScrollBox',