Skip to content

Commit

Permalink
Merge pull request #2 from Orchardxyz/dev-orchard
Browse files Browse the repository at this point in the history
feat: improve & optimize the `useManualScroll` hook
  • Loading branch information
NicoKam committed Jul 18, 2023
2 parents 87f1aa1 + 77717a4 commit 91b6ad1
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 38 deletions.
22 changes: 19 additions & 3 deletions packages/hooks/demo/useManualScroll/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@ import { useManualScroll } from '@orca-fe/hooks';

export default () => {
const ref = useRef<HTMLDivElement>(null);
const { run, scrollToLeft, scrollToRight, scrollToTop, scrollToBottom } = useManualScroll(ref, { scrollStep: 200 });
const { run, position, scrollToLeft, scrollToRight, scrollToTop, scrollToBottom } = useManualScroll(ref, { defaultScrollStep: 200 });

return (
<div>
<div ref={ref} style={{ width: 400, height: 400, overflow: 'auto', background: '#d3d3d3' }}>
<div style={{ width: 2000, height: 2000 }} />
<div
ref={ref}
style={{
width: 300,
height: 300,
border: '1px solid #AAA',
overflow: 'hidden',
display: 'grid',
gridTemplateColumns: '100px '.repeat(30),
}}
>
{new Array(30 * 30).fill(0)
.map((_, index) => (
<div key={index} style={{ width: '100px', height: '100px', border: '1px solid #CCC', boxSizing: 'border-box' }}>
{index}
</div>
))}
</div>
<div style={{ marginTop: 12, display: 'flex' }}>
<button
Expand Down Expand Up @@ -51,6 +66,7 @@ export default () => {
向下滚动
</button>
</div>
<p>{JSON.stringify(position)}</p>
</div>
);
};
2 changes: 1 addition & 1 deletion packages/hooks/demo/useWheel/horizScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useWheel, useManualScroll } from '@orca-fe/hooks';
export default () => {
// 容器 ref
const ref = useRef<HTMLDivElement>(null);
const manualScroll = useManualScroll(ref, { scrollStep: 400 });
const manualScroll = useManualScroll(ref, { defaultScrollStep: 300 });
const wheelState = useWheel(ref);

useEffect(() => {
Expand Down
42 changes: 24 additions & 18 deletions packages/hooks/docs/useManualScroll.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: useManualScroll 通过交互触发滚动的事件管理
title: useManualScroll 手动触发滚动

group:
title: hooks
Expand All @@ -8,7 +8,11 @@ group:

# useManualScroll 手动触发滚动

这是一个能够让你手动控制滚动事件的 hook
`useManualScroll` 在 ahooks 的 `useScroll` 基础上增加了手动触发滚动的能力,它同时透出了容器滚动时的 `position` 信息。
在需要通过用户交互事件手动地触发滚动的场景中,`useManualScroll` 可以代替 `useScroll` 进行使用。

你可以通过配置 `shouldUpdate` 来控制是否需要更新滚动信息, 它与在 `useScroll` 中的使用是一致的。
请注意,当 `shouldUpdate` 的结果为 `false` 时,`scrollToLeft``scrollToRight``scrollToTop``scrollToBottom` 的值也不会同步进行更新。

## 基本使用

Expand All @@ -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` |
43 changes: 27 additions & 16 deletions packages/hooks/src/useManualScroll.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -13,30 +14,36 @@ export type ManualScrollDirection = typeof MANUAL_SCROLL_UP | typeof MANUAL_SCRO

export type UseManualScrollOptions = {

/** 每次触发的滚动量 */
scrollStep?: number;
/** 默认触发的滚动量 */
defaultScrollStep?: number;

/** 滚动时长,单位毫秒 */
duration?: number;

/** 控制是否更新滚动信息(同 useScroll) */
shouldUpdate?: ScrollListenController;
};

// 基准帧间隔
const baseFrameInterval = 16.67;

type ManualScrollState = {

/** 当前滚动距离 */
scrollDistance: number;

/** 上一帧的时间 */
lastFrameTime: number | null;

/** 已执行的动画时长 */
animDuration: number;

/** 滚动方向 */
/** 当前滚动量 */
scrollStep: number;

/** 当前滚动方向 */
direction: ManualScrollDirection | null;

/** 当前累计滚动距离 */
scrollDistance: number;

/** 滚动偏移的误差值 */
scrollOffset: {
x: number;
Expand All @@ -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<ManualScrollState>({
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;
Expand All @@ -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(
Expand All @@ -95,7 +104,7 @@ export default function useManualScroll(target: BasicTarget, options: UseManualS
stopScrolling();
return;
}
// 帧间隔
// 当前帧间隔
const frameInterval = currentFrameInterval(ms, frameTime);
// 当前帧滚动量
const frameStep = currentFrameStep(frameInterval);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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),
};
}

1 comment on commit 91b6ad1

@vercel
Copy link

@vercel vercel bot commented on 91b6ad1 Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.