Created
October 31, 2014 05:19
-
-
Save maolion/e1a289050b280acbf5eb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var | |
Utils = require("Utils.js"), | |
Env = require("Env.js"), | |
Event = require("Event.js"), | |
Touch = require("Touch.js") | |
; | |
function TScroll(scroller, setting) { | |
var _this = this; | |
Event.call(this); | |
this._setting = setting = jQuery.extend({ | |
orient : TScroll.FREE, | |
pull : true, | |
pullLimit : 150, | |
easeing : 'ease', | |
momentum : true, | |
bounceEasing : 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', | |
bounceDuration : 400, | |
deceleration : 0.0006, | |
offset : { top : 0, left : 0 } | |
}, setting); | |
this._enabled = true; | |
this._scroller = scroller; | |
this._scrollerStyle = scroller[0].style; | |
this._wrapper = scroller.parent(); | |
this._offset = setting.offset; | |
this._updatePosition(0, 0); | |
this.refresh(); | |
this._wrapper.on("touchDown touchMove touchUp", function(event){ | |
if (!_this._enabled) return; | |
_this["_" + event.type] instanceof Function && _this["_" + event.type](event); | |
}); | |
this._scroller.on("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(event){ | |
if (event.target !== scroller[0] || !_this._isInTransition) return; | |
_this._resetTransitionDuration(); | |
_this.fireEvent("scroll",[_this.x, _this.y]); | |
if (!_this.resetPosition(setting.bounceDuration, setting.bounceEasing)) { | |
_this._isInTransition = false; | |
_this.fireEvent("scrollEnd"); | |
} | |
}); | |
} | |
TScroll.HORIZONTAL = 'H'; | |
TScroll.VERTICAL = 'V'; | |
TScroll.FREE = 'F'; | |
var api = TScroll.prototype = Utils.create(Event.prototype, TScroll); | |
api.enabled = function(){ | |
this._enabled = true; | |
}; | |
api.disabled = function(){ | |
this._enabled = false; | |
}; | |
api.isEnabled = function(){ | |
return this._enabled; | |
}; | |
api.refresh = function(){ | |
var pos = Utils.getComputedPosition(this._scroller[0]); | |
this._wrapperWidth = this._wrapper.width(); | |
this._wrapperHeight = this._wrapper.height(); | |
this._scrollerWidth = this._scroller.outerWidth(); | |
this._scrollerHeight = this._scroller.outerHeight(); | |
var y = this.y; | |
this.x = pos.x + this._offset.left; | |
this.y = pos.y + this._offset.top; | |
this.startX = this.x; | |
this.startY = this.y; | |
this._updatePosition(this.x, this.y); | |
this._limit = { | |
minX : 0, | |
maxX : this._wrapperWidth - this._scrollerWidth, | |
minY : 0, | |
maxY : Math.min(this._wrapperHeight - this._scrollerHeight, 0) | |
}; | |
}; | |
api.enabledPull = function(){ | |
this._setting.pull = true; | |
}; | |
api.disabledPull = function(){ | |
this._setting.pull = false; | |
}; | |
api.scrollTo = function(x, y, duration, easing) { | |
duration = ~~duration; | |
if (duration > 0) { | |
this._isInTransition = true; | |
} | |
this._resetTransitionDuration(duration); | |
this._resetTransitionTimingFunction(easing); | |
this._updatePosition(x, y); | |
if (duration <= 0) { | |
this.fireEvent("scroll", [this.x, this.y]); | |
this.fireEvent("scrollEnd"); | |
} | |
}; | |
api.resetPosition = function(duration, easing) { | |
var | |
orient = this._setting.orient, | |
minX = this._limit.minX, | |
maxX = this._limit.maxX, | |
minY = this._limit.minY, | |
maxY = this._limit.maxY, | |
x = this.x, | |
y = this.y, | |
duration = duration || 0, | |
orient = this._setting.orient | |
; | |
if (this._isPullHold) return false; | |
x = Math.max(Math.min(x, minX), maxX); | |
y = Math.max(Math.min(y, minY), maxY); | |
if (x === this.x && y === this.y) { | |
return false; | |
} | |
this.scrollTo(x, y, duration, easing); | |
return true; | |
} | |
api._touchDown = function(e) { | |
this._resetTransitionDuration(); | |
if (this._isInTransition) { | |
this._isInTransition = false; | |
var pos = Utils.getComputedPosition(this._scroller[0]); | |
this._updatePosition(pos.x, pos.y); | |
this.fireEvent("scroll", [this.x, this.y]); | |
this.fireEvent("scrollEnd"); | |
} | |
this.startTime = new Date().getTime() | |
this.direcX = 0; | |
this.direcY = 0; | |
this.lockedDire= null; | |
this.point = e.end; | |
this.refresh(); | |
this.fireEvent("scrollStart"); | |
}; | |
api._touchMove = function(e) { | |
var | |
orient = this._setting.orient, | |
minX = this._limit.minX, | |
maxX = this._limit.maxX, | |
minY = this._limit.minY, | |
maxY = this._limit.maxY, | |
nextX = e.end.x - this.point.x + this.x, | |
nextY = e.end.y - this.point.y + this.y, | |
now = new Date().getTime() | |
; | |
this.direcX = this.point.x < e.end.x ? 1 : this.point.x > e.end.x ? -1 : 0; | |
this.direcY = this.point.y < e.end.y ? 1 : this.point.y > e.end.y ? -1 : 0; | |
if ((nextX > minX || nextX < maxX) && orient !== TScroll.VERTICAL) { | |
var x = this.x; | |
if (this._setting.pull) { | |
var | |
limit = this._setting.pullLimit, | |
diff = nextX > minX ? Math.abs(x - minX) : Math.abs(x - maxX), | |
p = Math.min(diff/limit, 1) | |
; | |
nextX = this.x = Math.max(Math.min(this.x + (e.end.x - this.point.x) / Math.max(8 * p, 4), minX + limit), maxX - limit); | |
this.fireEvent("pull", [TScroll.HORIZONTAL, nextX > minX ? 1 : nextX < maxX ? -1 : 0, nextX, diff, nextX]); | |
} else { | |
nextX = this.y = nextX > minX ? minX : maxX; | |
} | |
} | |
if ((nextY > minY || nextY < maxY) && orient !== TScroll.HORIZONTAL) { | |
var y = this.y; | |
if (this._setting.pull) { | |
var | |
limit = this._setting.pullLimit, | |
diff = nextY > minY ? Math.abs(y - minY) : Math.abs(y - maxY), | |
p = Math.min(diff/limit, 1) | |
; | |
nextY = this.y = Math.max(Math.min(this.y + (e.end.y - this.point.y) / Math.max(8 * p, 4), minY + limit), maxY - limit); | |
this.fireEvent("pull", [TScroll.VERTICAL, nextY > minY ? 1 : nextY < maxY ? -1 : 0, diff, nextY]); | |
} else { | |
nextY = this.y = nextY > minY ? minY : maxY; | |
} | |
} | |
this.point = e.end; | |
var secPoint = e.path[1]; | |
switch(orient) { | |
case TScroll.VERTICAL: | |
if (this.lockedDire !== orient && !(Math.abs((secPoint.y - e.start.y) / (secPoint.x - e.start.x)) > 1)) { | |
return; | |
} | |
this.lockedDire = orient; | |
break; | |
case TScroll.HORIZONTAL: | |
if (this.lockedDire !== TScroll.VERTICAL && !(Math.abs((secPoint.x - e.start.x) / (secPoint.y - e.start.y)) > 1)) { | |
return; | |
} | |
this.lockedDire = orient; | |
break; | |
} | |
this._updatePosition(nextX, nextY); | |
this.fireEvent("scroll", [this.x, this.y]); | |
if ( now - this.startTime > 300 ) { | |
this.startTime = now; | |
this.startX = nextX; | |
this.startY = nextY; | |
} | |
}; | |
api._touchUp = function(event) { | |
var | |
_this = this, | |
duration = new Date().getTime() - this.startTime, | |
pullHold = false, | |
orient = this._setting.orient, | |
minX = this._limit.minX, | |
maxX = this._limit.maxX, | |
minY = this._limit.minY, | |
maxY = this._limit.maxY, | |
nextX = this.x, | |
nextY = this.y, | |
easing = null | |
; | |
this.endTime = new Date().getTime(); | |
this._isInTransition = false; | |
this._isPullHold = false; | |
if (nextX > minX || nextX < maxX || nextY > minY || nextY < maxY) { | |
var | |
limit = this._setting.pullLimit, | |
xDirec = nextX > minX ? 1 : nextX < maxX ? -1 : 0, | |
yDirec = nextY > minY ? 1 : nextY < maxY ? -1 : 0 | |
; | |
if (this._setting.pull) { | |
var hold = function(x, y, d, e) { | |
pullHold = true; | |
x = Math.min(Math.round(x) || 0, limit); | |
y = Math.min(Math.round(y) || 0, limit); | |
_this._isPullHold = x !== 0 || y !== 0; | |
_this.scrollTo( | |
xDirec === 1 ? minX + x : maxX - x, | |
yDirec === 1 ? minY + y : maxY - y, | |
d || _this._setting.bounceDuration, | |
e || _this._setting.bounceEasing | |
); | |
} | |
if (orient !== TScroll.VERTICAL && (nextX > minX || nextX < maxX)) { | |
this.fireEvent( | |
"pullHold", | |
[ TScroll.HORIZONTAL, xDirec, xDirec === 1 ? Math.abs(nextX - minX) : Math.abs(maxX - nextX), hold] | |
); | |
} | |
if (orient !== TScroll.HORIZONTAL && (nextY > minY || nextY < maxY)) { | |
this.fireEvent( | |
"pullHold", | |
[ TScroll.VERTICAL, yDirec, yDirec === 1 ? Math.abs(nextY - minY) : Math.abs(maxY - nextY), hold ] | |
); | |
} | |
} | |
} | |
if (pullHold) return; | |
if (this.resetPosition(this._setting.bounceDuration, this._setting.bounceEasing)) { | |
return; | |
} | |
this.scrollTo(this.x, this.y); | |
if (this._setting.momentum && duration < 300) { | |
var | |
momX = orient !== TScroll.VERTICAL ? momentum(this.x, this.startX, duration, minX, maxX, this._setting.pull ? this._wrapperWidth : 0, this._setting.deceleration) : { destination: this.x, duration: 0 }, | |
momY = orient !== TScroll.HORIZONTAL ? momentum(this.y, this.startY, duration, minY, maxY, this._setting.pull ? this._wrapperHeight : 0, this._setting.deceleration) : { destination: this.y, duration: 0 } | |
; | |
nextX = momX.destination; | |
nextY = momY.destination; | |
duration = Math.max(momX.duration, momY.duration); | |
} | |
if (nextX !== this.x || nextY !== this.y) { | |
easing = nextX > minX || nextX < maxX || nextY > minY || nextY < maxY ? this._setting.bounceEasing : this._setting.easing | |
this.scrollTo(nextX, nextY, duration, easing); | |
return; | |
} | |
this.fireEvent("scrollEnd"); | |
}; | |
api._updatePosition = function(x, y) { | |
switch(this._setting.orient){ | |
case TScroll.HORIZONTAL: this.y = y = 0; break; | |
case TScroll.VERTICAL: this.x = x = 0; break; | |
} | |
this._scrollerStyle[Env.CSSPROP.transform] = 'translate3d(' + x + 'px, ' + y + 'px, 0)'; | |
this.x = x; | |
this.y = y; | |
//this.fireEvent("scroll", [this.x, this.y]); | |
}; | |
api._resetTransitionDuration = function(duration){ | |
this._scrollerStyle[Env.CSSPROP.transitionDuration] = (duration || '0')+'ms'; | |
}; | |
api._resetTransitionTimingFunction = function(easing){ | |
this._scrollerStyle[Env.CSSPROP.transitionTimingFunction] = easing || ''; | |
}; | |
function momentum(current, start, time, min, max, wrapperSize, deceleration) { | |
var | |
distance = current - start, | |
speed = Math.abs(distance) / time, | |
destination = deceleration === undefined ? 0.0006 : deceleration, | |
destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ), | |
duration = speed / deceleration | |
; | |
if ( destination < max ) { | |
destination = wrapperSize ? max - ( wrapperSize / 2.5 * ( speed / 8 ) ) : max; | |
distance = Math.abs(destination - current); | |
duration = distance / speed; | |
} else if ( destination > min ) { | |
destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : min; | |
distance = Math.abs(current) + destination; | |
duration = distance / speed; | |
} | |
return { | |
destination: Math.round(destination), | |
duration: duration | |
}; | |
} | |
module.exports = TScroll; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment