Skip to content

Instantly share code, notes, and snippets.

@maolion
Created October 31, 2014 05:19
Show Gist options
  • Save maolion/e1a289050b280acbf5eb to your computer and use it in GitHub Desktop.
Save maolion/e1a289050b280acbf5eb to your computer and use it in GitHub Desktop.
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