From 77717a43c690400929d5aade1fdb0056d5dda327 Mon Sep 17 00:00:00 2001 From: Orchard Date: Tue, 18 Jul 2023 14:27:19 +0800 Subject: [PATCH] feat: improve & optimize the `useManualScroll` hook --- packages/hooks/demo/useManualScroll/basic.tsx | 22 ++++++++-- packages/hooks/demo/useWheel/horizScroll.tsx | 2 +- packages/hooks/docs/useManualScroll.md | 42 ++++++++++-------- packages/hooks/src/useManualScroll.ts | 43 ++++++++++++------- 4 files changed, 71 insertions(+), 38 deletions(-) diff --git a/packages/hooks/demo/useManualScroll/basic.tsx b/packages/hooks/demo/useManualScroll/basic.tsx index 605b05ea..23d7da17 100644 --- a/packages/hooks/demo/useManualScroll/basic.tsx +++ b/packages/hooks/demo/useManualScroll/basic.tsx @@ -3,12 +3,27 @@ import { useManualScroll } from '@orca-fe/hooks'; export default () => { const ref = useRef(null); - const { run, scrollToLeft, scrollToRight, scrollToTop, scrollToBottom } = useManualScroll(ref, { scrollStep: 200 }); + const { run, position, scrollToLeft, scrollToRight, scrollToTop, scrollToBottom } = useManualScroll(ref, { defaultScrollStep: 200 }); return (
-
-
+
+ {new Array(30 * 30).fill(0) + .map((_, index) => ( +
+ {index} +
+ ))}
+

{JSON.stringify(position)}

); }; diff --git a/packages/hooks/demo/useWheel/horizScroll.tsx b/packages/hooks/demo/useWheel/horizScroll.tsx index 066a7c63..4a5c5e24 100644 --- a/packages/hooks/demo/useWheel/horizScroll.tsx +++ b/packages/hooks/demo/useWheel/horizScroll.tsx @@ -4,7 +4,7 @@ import { useWheel, useManualScroll } from '@orca-fe/hooks'; export default () => { // 容器 ref const ref = useRef(null); - const manualScroll = useManualScroll(ref, { scrollStep: 400 }); + const manualScroll = useManualScroll(ref, { defaultScrollStep: 300 }); const wheelState = useWheel(ref); useEffect(() => { diff --git a/packages/hooks/docs/useManualScroll.md b/packages/hooks/docs/useManualScroll.md index 3ecb703e..312a9e3b 100644 --- a/packages/hooks/docs/useManualScroll.md +++ b/packages/hooks/docs/useManualScroll.md @@ -1,5 +1,5 @@ --- -title: useManualScroll 通过交互触发滚动的事件管理 +title: useManualScroll 手动触发滚动 group: title: hooks @@ -8,7 +8,11 @@ group: # useManualScroll 手动触发滚动 -这是一个能够让你手动控制滚动事件的 hook +`useManualScroll` 在 ahooks 的 `useScroll` 基础上增加了手动触发滚动的能力,它同时透出了容器滚动时的 `position` 信息。 +在需要通过用户交互事件手动地触发滚动的场景中,`useManualScroll` 可以代替 `useScroll` 进行使用。 + +你可以通过配置 `shouldUpdate` 来控制是否需要更新滚动信息, 它与在 `useScroll` 中的使用是一致的。 +请注意,当 `shouldUpdate` 的结果为 `false` 时,`scrollToLeft`、`scrollToRight`、`scrollToTop`、`scrollToBottom` 的值也不会同步进行更新。 ## 基本使用 @@ -17,29 +21,31 @@ group: ## API ```ts | pure -const { run, scrollToLeft, scrollToRight, scrollToTop, scrollToBottom } = useManualScroll(target, options); +const { run, position, scrollToLeft, scrollToRight, scrollToTop, scrollToBottom } = useManualScroll(target, options); ``` ## Params -| 属性 | 说明 | 类型 | 默认值 | 版本 | -| ------- | --------------------- | ------------------------ | ------ | ---- | -| target | DOM 节点或者 Ref 对象 | `BasicTarget` | `-` | `-` | -| options | 额外的配置项 | `UseManualScrollOptions` | `-` | `-` | +| 属性 | 说明 | 类型 | 默认值 | +| ------- | --------------------- | ------------------------ | ------ | +| target | DOM 节点或者 Ref 对象 | `BasicTarget` | `-` | +| options | 额外的配置项 | `UseManualScrollOptions` | `-` | ### UseManualScrollOptions -| 属性 | 说明 | 类型 | 默认值 | 版本 | -| ---------- | ------------------------ | -------- | ------ | ---- | -| scrollStep | 每次触发的滚动量(正数) | `number` | `200` | `-` | -| duration | 滚动时长,单位毫秒 | `number` | `300` | `-` | +| 属性 | 说明 | 类型 | 默认值 | 版本 | +| ----------------- | -------------------- | -------------------------------------------- | ------------ | ---- | +| defaultScrollStep | 默认滚动量 | `number` | `200` | `-` | +| duration | 滚动时长,单位毫秒 | `number` | `300` | `-` | +| shouldUpdate | 控制是否更新滚动信息 | `({ top: number, left: number }) => boolean` | `() => true` | `-` | ## Result -| 参数 | 说明 | 类型 | 默认值 | 版本 | -| -------------- | ---------------- | -------------------------------------------- | ------- | ---- | -| run | 触发容器元素滚动 | `(direction: ManualScrollDirection) => void` | `-` | `-` | -| scrollToLeft | 是否滚动至最左侧 | `boolean` | `true` | `-` | -| scrollToRight | 是否滚动至最右侧 | `boolean` | `false` | `-` | -| scrollToTop | 是否滚动至顶部 | `boolean` | `true` | `-` | -| scrollToBottom | 是否滚动至底部 | `boolean` | `false` | `-` | +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | -------------------------------- | ----------------------------------------------------------------- | ----------- | +| run | 触发容器滚动(支持自定义滚动量) | `(direction: ManualScrollDirection, scrollStep?: number) => void` | `-` | +| position | 滚动容器当前的滚动位置 | `{ left: number, top: number } \| undefined` | `undefined` | +| scrollToLeft | 是否滚动至最左侧 | `boolean` | `true` | +| scrollToRight | 是否滚动至最右侧 | `boolean` | `false` | +| scrollToTop | 是否滚动至顶部 | `boolean` | `true` | +| scrollToBottom | 是否滚动至底部 | `boolean` | `false` | diff --git a/packages/hooks/src/useManualScroll.ts b/packages/hooks/src/useManualScroll.ts index bb8ad744..3b868cee 100644 --- a/packages/hooks/src/useManualScroll.ts +++ b/packages/hooks/src/useManualScroll.ts @@ -1,6 +1,7 @@ import { useBoolean, useMemoizedFn, useScroll, useSize, useThrottleFn } from 'ahooks'; import { useEffect, useRef } from 'react'; import { round } from 'lodash-es'; +import type { ScrollListenController } from 'ahooks/lib/useScroll'; import type { BasicTarget } from './utils/domTarget'; import { getTargetElement } from './utils/domTarget'; import useAnimationFrame from './useAnimationFrame'; @@ -13,11 +14,14 @@ export type ManualScrollDirection = typeof MANUAL_SCROLL_UP | typeof MANUAL_SCRO export type UseManualScrollOptions = { - /** 每次触发的滚动量 */ - scrollStep?: number; + /** 默认触发的滚动量 */ + defaultScrollStep?: number; /** 滚动时长,单位毫秒 */ duration?: number; + + /** 控制是否更新滚动信息(同 useScroll) */ + shouldUpdate?: ScrollListenController; }; // 基准帧间隔 @@ -25,18 +29,21 @@ const baseFrameInterval = 16.67; type ManualScrollState = { - /** 当前滚动距离 */ - scrollDistance: number; - /** 上一帧的时间 */ lastFrameTime: number | null; /** 已执行的动画时长 */ animDuration: number; - /** 滚动方向 */ + /** 当前滚动量 */ + scrollStep: number; + + /** 当前滚动方向 */ direction: ManualScrollDirection | null; + /** 当前累计滚动距离 */ + scrollDistance: number; + /** 滚动偏移的误差值 */ scrollOffset: { x: number; @@ -47,23 +54,23 @@ type ManualScrollState = { const isHorizScroll = (direction: ManualScrollDirection) => direction === MANUAL_SCROLL_LEFT || direction === MANUAL_SCROLL_RIGHT; export default function useManualScroll(target: BasicTarget, options: UseManualScrollOptions = {}) { - const { scrollStep = 200, duration = 300 } = options; - const baseFrameStep = scrollStep / (duration / baseFrameInterval); + const { defaultScrollStep = 200, duration = 300, shouldUpdate = () => true } = options; const _this = useRef({ scrollDistance: 0, lastFrameTime: null, animDuration: 0, direction: null, + scrollStep: defaultScrollStep, scrollOffset: { x: 0, y: 0 }, }).current; const [scrolling, { setTrue: startScrolling, setFalse: stopScrolling }] = useBoolean(false); - const position = useScroll(target); + const dom = getTargetElement(target); const size = useSize(target); + const position = useScroll(target, shouldUpdate); - const dom = getTargetElement(target); const scrollToLeft = position?.left === 0; const scrollToRight = Math.ceil((position?.left ?? 0) + (size?.width ?? 0)) >= (dom?.scrollWidth ?? 0); const scrollToTop = position?.top === 0; @@ -74,19 +81,21 @@ export default function useManualScroll(target: BasicTarget, options: UseManualS if (!_this.lastFrameTime) { _this.lastFrameTime = frameTime; } - const _frameInterval = ms - _this.lastFrameTime || baseFrameInterval; + const frameInterval = ms - _this.lastFrameTime || baseFrameInterval; - return _this.animDuration + _frameInterval > duration ? duration - _this.animDuration : _frameInterval; + return _this.animDuration + frameInterval > duration ? duration - _this.animDuration : frameInterval; }); // 获取当前帧滚动量 const currentFrameStep = useMemoizedFn((frameInterval: number) => { + // 基准帧滚动量 + const baseFrameStep = _this.scrollStep / (duration / baseFrameInterval); // 帧间隔偏差比例 const deviationRatio = frameInterval / baseFrameInterval; // 当前帧滚动量 const frameStep = deviationRatio * baseFrameStep; - return _this.scrollDistance + frameStep > scrollStep ? scrollStep - _this.scrollDistance : frameStep; + return _this.scrollDistance + frameStep > _this.scrollStep ? _this.scrollStep - _this.scrollDistance : frameStep; }); const rafHandler = useAnimationFrame( @@ -95,7 +104,7 @@ export default function useManualScroll(target: BasicTarget, options: UseManualS stopScrolling(); return; } - // 帧间隔 + // 当前帧间隔 const frameInterval = currentFrameInterval(ms, frameTime); // 当前帧滚动量 const frameStep = currentFrameStep(frameInterval); @@ -137,8 +146,9 @@ export default function useManualScroll(target: BasicTarget, options: UseManualS { manual: true }, ); - const run = (direction: ManualScrollDirection) => { + const run = (direction: ManualScrollDirection, scrollStep: number = defaultScrollStep) => { _this.direction = direction; + _this.scrollStep = scrollStep; if (direction === MANUAL_SCROLL_LEFT && scrollToLeft) return; if (direction === MANUAL_SCROLL_RIGHT && scrollToRight) return; @@ -174,10 +184,11 @@ export default function useManualScroll(target: BasicTarget, options: UseManualS }, [scrolling]); return { - run: useMemoizedFn(throttleRun), + position, scrollToLeft, scrollToRight, scrollToTop, scrollToBottom, + run: useMemoizedFn(throttleRun), }; }