UNPKG

overscroll

Version:
416 lines (376 loc) 11.8 kB
import is from 'whatitis'; import Hammer from 'hammerjs'; import browser from './utils/browser'; import addEventListener from './utils/dom/addDomEventListener'; import { requestAnimFrame } from './utils/requestAnimationFrame'; 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; }; export 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, is.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(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 = browser.firefox ? 'DOMMouseScroll' : 'mousewheel'; handleDestroy(addEventListener(target, 'mousedown', scrollStop).remove); handleDestroy(addEventListener(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 Hammer.Manager(target); manager.add(new Hammer.Pan({ direction: Hammer.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(addEventListener(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 }; } }; } }; });