overscroll
Version:
433 lines (383 loc) • 12.4 kB
JavaScript
'use strict';
exports.__esModule = true;
var _whatitis = require('whatitis');
var _whatitis2 = _interopRequireDefault(_whatitis);
var _hammerjs = require('hammerjs');
var _hammerjs2 = _interopRequireDefault(_hammerjs);
var _browser = require('./utils/browser');
var _browser2 = _interopRequireDefault(_browser);
var _addDomEventListener = require('./utils/dom/addDomEventListener');
var _addDomEventListener2 = _interopRequireDefault(_addDomEventListener);
var _requestAnimationFrame = require('./utils/requestAnimationFrame');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function sign(number) {
return number > 0 ? 1 : -1;
}
var a = function a(v) {
return v > 30 ? 1.5 : (1 - Math.cos(Math.PI * v / 30)) / 2 * 1.5 * 29 / 30 + 1 / 30;
};
var easeInOut = function easeInOut(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b; // eslint-disable-line
return -c / 2 * (--t * (t - 2) - 1) + b;
};
exports['default'] = function (scope) {
var handleDestroy = scope.handleDestroy,
overscroll = scope.overscroll,
hasScrollY = scope.hasScrollY,
target = scope.target,
html = scope.html,
onScroll = scope.onScroll,
isPageScroll = scope.isPageScroll,
resetCache = scope.resetCache,
scrollable = scope.scrollable,
isTop = scope.isTop,
isBottom = scope.isBottom,
isLeft = scope.isLeft,
isRight = scope.isRight,
getFromRange = scope.getFromRange,
hasScrollX = scope.hasScrollX,
onBeforeScroll = scope.onBeforeScroll,
onAfterScroll = scope.onAfterScroll,
scrollX = scope.scrollX,
scrollY = scope.scrollY,
getScroll = scope.getScroll,
dragable = scope.dragable,
touchable = scope.touchable;
var xory = function xory(func) {
return function (x, y) {
if (scrollX && scrollY) {
func(x, y);
} else if (scrollX) {
func(x);
} else {
func(undefined, _whatitis2['default'].Defined(y) ? y : x);
}
};
};
var abs = Math.abs;
var max = Math.max;
var min = Math.min;
var setScroll = xory(scope.setScroll);
var getVelocity = function getVelocity(v) {
return v === 0 ? 0 : getFromRange(2, 50)(v);
};
function runAnimFrame() {
var vx = 0;
var vy = 0;
var dx = 0;
var dy = 0;
var ovx = 0;
var ovy = 0;
var odx = 0;
var ody = 0;
var posX = 0;
var posY = 0;
var lastTime = 0;
var timePointX = 0;
var timePointY = 0;
handleDestroy((0, _requestAnimationFrame.requestAnimFrame)(function (time) {
var scrollLeft = overscroll.scrollLeft,
scrollTop = overscroll.scrollTop;
// 滚动到指定位置
if (dx !== 0 || dy !== 0) {
if (hasScrollY() && dy !== 0) {
timePointY = min(timePointY, 1);
var start = posY - ody;
var delta = sign(ody) * easeInOut(timePointY, 0, abs(ody), 1);
dy = ody - delta;
vy = delta - (scrollTop - start);
// 向下/向上
if (isBottom() && vy > 0 || isTop() && vy < 0) {
vy = 0;
dy = 0;
}
if (timePointY === 1) {
dy = 0;
} else {
timePointY += (time - lastTime) / 1000;
}
}
if (hasScrollX() && dx !== 0) {
timePointX = min(timePointX, 1);
var _start = posX - odx;
var _delta = sign(odx) * easeInOut(timePointX, 0, abs(odx), 1);
dx = odx - _delta;
vx = _delta - (scrollLeft - _start);
// 向右/向左
if (isRight() && vx > 0 || isLeft() && vx < 0) {
vx = 0;
dx = 0;
}
if (timePointX === 1) {
dx = 0;
} else {
timePointX += (time - lastTime) / 1000;
}
}
}
// 正常的滚动
else {
if (hasScrollY() && vy !== 0) {
vy = sign(vy) * max(0, abs(vy) - a(abs(vy)));
// 向下/向上
if (isBottom() && vy > 0 || isTop() && vy < 0) {
vy = 0;
}
}
if (hasScrollX() && vx !== 0) {
vx = sign(vx) * max(0, abs(vx) - a(abs(vx)));
// 向右/向左
if (isRight() && vx > 0 || isLeft() && vx < 0) {
vx = 0;
}
}
}
if (!overscroll.scrolling && ovx === 0 && ovy === 0 && dx === 0 && dy === 0 && (vx !== 0 || vy !== 0)) {
onBeforeScroll();
}
if (vx !== 0 || vy !== 0) {
var nextX = scrollLeft + vx;
var nextY = scrollTop + vy;
if (overscroll.scrollHeight === overscroll.clientHeight) {
nextY = 0;
dy = 0;
vy = 0;
ody = 0;
posY = 0;
timePointY = 0;
}
if (overscroll.scrollWidth === overscroll.clientWidth) {
nextX = 0;
dx = 0;
vx = 0;
odx = 0;
posX = 0;
timePointX = 0;
}
setScroll(nextX, nextY);
// setScroll( scrollLeft + vx, scrollTop + vy );
// if ( dx !== 0 || dy !== 0 ) {
onScroll();
// }
}
// 指定距离移动时:速度清空必须 setScroll 之后
if (dx === 0 && posX !== 0) {
vx = 0;
odx = 0;
posX = 0;
timePointX = 0;
}
if (dy === 0 && posY !== 0) {
vy = 0;
ody = 0;
posY = 0;
timePointY = 0;
}
if (overscroll.scrolling && vx === 0 && vy === 0 && dx === 0 && dy === 0 && (ovx !== 0 || ovy !== 0)) {
setScroll();
onAfterScroll();
}
// 用于计算滚动的开始和结束
ovx = vx;
ovy = vy;
// 用于计算时间间隔
lastTime = time;
}).cancel);
return {
scrollMove: xory(function () {
var velocityX = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var velocityY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (vy > 0 !== velocityY > 0) {
vy = 0;
}
if (vx > 0 !== velocityX > 0) {
vx = 0;
}
vy += sign(velocityY) * getVelocity(abs(velocityY));
vy = sign(velocityY) * getVelocity(abs(vy));
vx += sign(velocityX) * getVelocity(abs(velocityX));
vx = sign(velocityX) * getVelocity(abs(vx));
}),
scrollTo: xory(function () {
var positionX = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : overscroll.scrollLeft;
var positionY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : overscroll.scrollTop;
// 一个动作没有结束是开始另一个动作
if (timePointX !== 0 || timePointY !== 0) {
vx = 0;
vy = 0;
dx = 0;
dy = 0;
ovx = 0;
ovy = 0;
odx = 0;
ody = 0;
posX = 0;
posY = 0;
timePointX = 0;
timePointY = 0;
} else {
onBeforeScroll();
}
var scrollTop = overscroll.scrollTop,
scrollLeft = overscroll.scrollLeft;
dx = positionX - scrollLeft;
dy = positionY - scrollTop;
posX = positionX;
posY = positionY;
odx = dx;
ody = dy;
}),
scrollStop: function scrollStop() {
if (vx !== 0 || vy !== 0) {
onAfterScroll();
}
vx = 0;
vy = 0;
dx = 0;
dy = 0;
ovx = 0;
ovy = 0;
odx = 0;
ody = 0;
posX = 0;
posY = 0;
timePointX = 0;
timePointY = 0;
setScroll();
}
};
}
function runMouseAction(_ref) {
var scrollMove = _ref.scrollMove,
scrollStop = _ref.scrollStop;
var eventName = _browser2['default'].firefox ? 'DOMMouseScroll' : 'mousewheel';
handleDestroy((0, _addDomEventListener2['default'])(target, 'mousedown', scrollStop).remove);
handleDestroy((0, _addDomEventListener2['default'])(isPageScroll ? html : target, eventName, function (event) {
var deltaY = event.deltaY,
deltaX = event.deltaX,
shiftKey = event.shiftKey;
var targetScrollable = scrollable(event.target);
var targetUnscrollableY = !targetScrollable.top && deltaY >= 0 || !targetScrollable.bottom && deltaY <= 0;
var targetUnscrollableX = !targetScrollable.left && -deltaX >= 0 || !targetScrollable.right && -deltaX <= 0;
var scrollTop = overscroll.scrollTop;
var scrollLeft = overscroll.scrollLeft;
var x = scrollLeft;
var y = scrollTop;
if (!targetUnscrollableX) {
x = deltaX;
}
if (!targetUnscrollableY) {
y = -deltaY;
}
if (x !== scrollLeft || y !== scrollTop) {
resetCache(scope);
scrollMove(shiftKey && x === 0 ? y : x, y);
event.preventDefault();
event.stopPropagation();
}
}).remove);
}
function runHammer(_ref2) {
var scrollMove = _ref2.scrollMove,
scrollStop = _ref2.scrollStop;
var handleTarget = void 0;
var lastDeltaX = void 0;
var lastDeltaY = void 0;
var manager = new _hammerjs2['default'].Manager(target);
manager.add(new _hammerjs2['default'].Pan({ direction: _hammerjs2['default'].DIRECTION_ALL, threshold: 0 }));
manager.on('panstart panmove panend', function (event) {
event.preventDefault();
if (!dragable && event.pointerType === 'mouse') {
return;
}
if (!touchable && event.pointerType === 'touch') {
return;
}
var type = event.type,
velocityY = event.velocityY,
velocityX = event.velocityX,
deltaX = event.deltaX,
deltaY = event.deltaY;
var targetScrollable = scrollable(handleTarget || event.target);
var targetUnscrollableX = !targetScrollable.left && velocityX >= 0 || !targetScrollable.right && velocityX <= 0;
var targetUnscrollableY = !targetScrollable.top && velocityY >= 0 || !targetScrollable.bottom && velocityY <= 0;
var scrollTop = overscroll.scrollTop;
var scrollLeft = overscroll.scrollLeft;
var x = scrollLeft;
var y = scrollTop;
if (type === 'panstart') {
resetCache(scope);
lastDeltaX = 0;
lastDeltaY = 0;
handleTarget = event.target;
} else if (type === 'panend') {
handleTarget = null;
if (!targetUnscrollableX) {
x = -velocityX * 20;
}
if (!targetUnscrollableY) {
y = -velocityY * 20;
}
if (x !== scrollLeft || y !== scrollTop) {
scrollMove(x, y);
} else if (overscroll.scrolling) {
onAfterScroll();
}
}
// panmove
else {
if (!targetUnscrollableX) {
x = scrollLeft - (deltaX - lastDeltaX);
}
if (!targetUnscrollableY) {
y = scrollTop - (deltaY - lastDeltaY);
}
if (x !== scrollLeft || y !== scrollTop) {
if (!overscroll.scrolling) {
onBeforeScroll();
}
setScroll(x, y);
onScroll();
}
}
lastDeltaX = deltaX;
lastDeltaY = deltaY;
});
handleDestroy(function () {
return manager.destroy();
});
if (touchable) {
handleDestroy((0, _addDomEventListener2['default'])(target, 'touchstart', scrollStop).remove);
}
}
return {
run: function run() {
resetCache(scope);
var anim = runAnimFrame();
runMouseAction(anim);
if (dragable || touchable) {
runHammer(anim);
}
return {
scrollTo: function scrollTo(positionX, positionY, noAnimation) {
resetCache(scope);
if (noAnimation === true) {
setScroll(positionX, positionY);
return;
}
anim.scrollStop();
anim.scrollTo(positionX, positionY);
},
position: function position() {
var _getScroll = getScroll(),
left = _getScroll.left,
top = _getScroll.top;
return {
scrollTop: top,
scrollLeft: left
};
}
};
}
};
};