@orca-fe/hooks
Version:
React Hooks Collections
202 lines (191 loc) • 8.56 kB
JavaScript
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import { useBoolean, useLatest, useMemoizedFn, useRafState, useSize, useThrottleFn } from 'ahooks';
import { useEffect, useRef } from 'react';
import { round } from 'lodash-es';
import { getTargetElement } from "./utils/domTarget";
import useAnimationFrame from "./useAnimationFrame";
import useEffectWithTarget from "./useEffectWithTarget";
export var MANUAL_SCROLL_UP = 'up'; // 向上滚动
export var MANUAL_SCROLL_DOWN = 'down'; // 向下滚动
export var MANUAL_SCROLL_LEFT = 'left'; // 向左滚动
export var MANUAL_SCROLL_RIGHT = 'right'; // 向右滚动
// 基准帧间隔
var baseFrameInterval = 16.67;
// 是否水平滚动
var isHorizScroll = direction => direction === MANUAL_SCROLL_LEFT || direction === MANUAL_SCROLL_RIGHT;
export default function useManualScroll(target, options = {}) {
var _options$defaultScrol = options.defaultScrollStep,
defaultScrollStep = _options$defaultScrol === void 0 ? 200 : _options$defaultScrol,
_options$duration = options.duration,
duration = _options$duration === void 0 ? 300 : _options$duration,
_options$shouldUpdate = options.shouldUpdate,
shouldUpdate = _options$shouldUpdate === void 0 ? () => true : _options$shouldUpdate;
var _this = useRef({
scrollDistance: 0,
lastFrameTime: null,
animDuration: 0,
direction: null,
scrollStep: defaultScrollStep,
scrollOffset: {
x: 0,
y: 0
}
}).current;
var _useBoolean = useBoolean(false),
_useBoolean2 = _slicedToArray(_useBoolean, 2),
scrolling = _useBoolean2[0],
_useBoolean2$ = _useBoolean2[1],
startScrolling = _useBoolean2$.setTrue,
stopScrolling = _useBoolean2$.setFalse;
var dom = getTargetElement(target);
var size = useSize(target);
// 透传出的容器滚动位置信息(依赖 shouldUpdate)
var _useRafState = useRafState(),
_useRafState2 = _slicedToArray(_useRafState, 2),
position = _useRafState2[0],
setPosition = _useRafState2[1];
// 保存容器最新的滚动位置信息
var latestPositionRef = useRef();
var shouldUpdateRef = useLatest(shouldUpdate);
// 根据当前位置获取各方位滚动状态
var scrollBoundaryState = useMemoizedFn(currentPosition => {
var _currentPosition$left, _size$width, _dom$scrollWidth, _currentPosition$top, _size$height, _dom$scrollHeight;
var scrollToLeft = (currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.left) === 0;
var scrollToRight = Math.ceil(((_currentPosition$left = currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.left) !== null && _currentPosition$left !== void 0 ? _currentPosition$left : 0) + ((_size$width = size === null || size === void 0 ? void 0 : size.width) !== null && _size$width !== void 0 ? _size$width : 0)) >= ((_dom$scrollWidth = dom === null || dom === void 0 ? void 0 : dom.scrollWidth) !== null && _dom$scrollWidth !== void 0 ? _dom$scrollWidth : 0);
var scrollToTop = (currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.top) === 0;
var scrollToBottom = Math.ceil(((_currentPosition$top = currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.top) !== null && _currentPosition$top !== void 0 ? _currentPosition$top : 0) + ((_size$height = size === null || size === void 0 ? void 0 : size.height) !== null && _size$height !== void 0 ? _size$height : 0)) >= ((_dom$scrollHeight = dom === null || dom === void 0 ? void 0 : dom.scrollHeight) !== null && _dom$scrollHeight !== void 0 ? _dom$scrollHeight : 0);
return {
scrollToLeft,
scrollToRight,
scrollToTop,
scrollToBottom
};
});
// 获取当前帧间隔
var currentFrameInterval = useMemoizedFn((ms, frameTime) => {
if (!_this.lastFrameTime) {
_this.lastFrameTime = frameTime;
}
var frameInterval = ms - _this.lastFrameTime || baseFrameInterval;
return _this.animDuration + frameInterval > duration ? duration - _this.animDuration : frameInterval;
});
// 获取当前帧滚动量
var currentFrameStep = useMemoizedFn(frameInterval => {
// 基准帧滚动量
var baseFrameStep = _this.scrollStep / (duration / baseFrameInterval);
// 帧间隔偏差比例
var deviationRatio = frameInterval / baseFrameInterval;
// 当前帧滚动量
var frameStep = deviationRatio * baseFrameStep;
return _this.scrollDistance + frameStep > _this.scrollStep ? _this.scrollStep - _this.scrollDistance : frameStep;
});
// 是否抵达边界
var isReachToBoundary = useMemoizedFn(() => {
var latestPosition = scrollBoundaryState(latestPositionRef.current);
if (_this.direction === MANUAL_SCROLL_LEFT) return latestPosition.scrollToLeft;
if (_this.direction === MANUAL_SCROLL_RIGHT) return latestPosition.scrollToRight;
if (_this.direction === MANUAL_SCROLL_UP) return latestPosition.scrollToTop;
if (_this.direction === MANUAL_SCROLL_DOWN) return latestPosition.scrollToBottom;
return false;
});
// modify from `useScroll`
useEffectWithTarget(() => {
var el = getTargetElement(target);
if (!el) {
return;
}
var updatePosition = () => {
var _shouldUpdateRef$curr;
var newPosition = {
left: el.scrollLeft,
top: el.scrollTop
};
if ((_shouldUpdateRef$curr = shouldUpdateRef.current) !== null && _shouldUpdateRef$curr !== void 0 && _shouldUpdateRef$curr.call(shouldUpdateRef, newPosition)) {
setPosition(newPosition);
}
latestPositionRef.current = newPosition;
};
updatePosition();
el.addEventListener('scroll', updatePosition);
// eslint-disable-next-line consistent-return
return () => {
el.removeEventListener('scroll', updatePosition);
};
}, [], target);
var rafHandler = useAnimationFrame((ms, frameTime) => {
if (!dom || !_this.direction || _this.animDuration >= duration) {
stopScrolling();
return;
}
if (isReachToBoundary()) {
if (isHorizScroll(_this.direction)) _this.scrollOffset.x = 0;else _this.scrollOffset.y = 0;
stopScrolling();
return;
}
// 当前帧间隔
var frameInterval = currentFrameInterval(ms, frameTime);
// 当前帧滚动量
var frameStep = currentFrameStep(frameInterval);
// 累计滚动量
_this.scrollDistance += frameStep;
// 累计动画时间
_this.animDuration += frameInterval;
var xScrollValue = 0;
var yScrollValue = 0;
// 向左滚动
if (_this.direction === MANUAL_SCROLL_LEFT) {
xScrollValue = round(dom.scrollLeft - frameStep, 2) + _this.scrollOffset.x;
}
// 向右滚动
if (_this.direction === MANUAL_SCROLL_RIGHT) {
xScrollValue = round(dom.scrollLeft + frameStep, 2) + _this.scrollOffset.x;
}
// 向上滚动
if (_this.direction === MANUAL_SCROLL_UP) {
yScrollValue = round(dom.scrollTop - frameStep, 2) + _this.scrollOffset.y;
}
// 向下滚动
if (_this.direction === MANUAL_SCROLL_DOWN) {
yScrollValue = round(dom.scrollTop + frameStep, 2) + _this.scrollOffset.y;
}
// 滚动并更新误差值
if (isHorizScroll(_this.direction)) {
dom.scrollLeft = xScrollValue;
_this.scrollOffset.x = xScrollValue - dom.scrollLeft;
} else {
dom.scrollTop = yScrollValue;
_this.scrollOffset.y = yScrollValue - dom.scrollTop;
}
}, {
manual: true
});
var run = (direction, scrollStep = defaultScrollStep) => {
_this.direction = direction;
_this.scrollStep = scrollStep;
var latestPosition = scrollBoundaryState(latestPositionRef.current);
if (direction === MANUAL_SCROLL_LEFT && latestPosition.scrollToLeft) return;
if (direction === MANUAL_SCROLL_RIGHT && latestPosition.scrollToRight) return;
if (direction === MANUAL_SCROLL_UP && latestPosition.scrollToTop) return;
if (direction === MANUAL_SCROLL_DOWN && latestPosition.scrollToBottom) return;
startScrolling();
};
var _useThrottleFn = useThrottleFn(run, {
wait: duration
}),
throttleRun = _useThrottleFn.run;
useEffect(() => {
if (scrolling) {
_this.animDuration = 0;
_this.scrollDistance = 0;
_this.lastFrameTime = null;
rafHandler.start();
} else {
rafHandler.stop();
}
}, [scrolling]);
return _objectSpread({
position,
run: useMemoizedFn(throttleRun)
}, scrollBoundaryState(position));
}