-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathuseScrollBasedChunks.ts
92 lines (77 loc) · 2.83 KB
/
useScrollBasedChunks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import React from 'react';
import {throttle} from 'lodash';
import {calculateElementOffsetTop} from './utils';
interface UseScrollBasedChunksProps {
parentRef: React.RefObject<HTMLElement>;
tableRef: React.RefObject<HTMLElement>;
totalItems: number;
rowHeight: number;
chunkSize: number;
overscanCount?: number;
}
const DEFAULT_OVERSCAN_COUNT = 1;
const THROTTLE_DELAY = 100;
export const useScrollBasedChunks = ({
parentRef,
tableRef,
totalItems,
rowHeight,
chunkSize,
overscanCount = DEFAULT_OVERSCAN_COUNT,
}: UseScrollBasedChunksProps): boolean[] => {
const chunksCount = React.useMemo(
() => Math.ceil(totalItems / chunkSize),
[chunkSize, totalItems],
);
const [startChunk, setStartChunk] = React.useState(0);
const [endChunk, setEndChunk] = React.useState(
Math.min(overscanCount, Math.max(chunksCount - 1, 0)),
);
const calculateVisibleRange = React.useCallback(() => {
const container = parentRef?.current;
const table = tableRef.current;
if (!container || !table) {
return null;
}
const tableOffset = calculateElementOffsetTop(table, container);
const containerScroll = container.scrollTop;
const visibleStart = Math.max(containerScroll - tableOffset, 0);
const visibleEnd = visibleStart + container.clientHeight;
const start = Math.max(Math.floor(visibleStart / rowHeight / chunkSize) - overscanCount, 0);
const end = Math.min(
Math.floor(visibleEnd / rowHeight / chunkSize) + overscanCount,
Math.max(chunksCount - 1, 0),
);
return {start, end};
}, [parentRef, tableRef, rowHeight, chunkSize, overscanCount, chunksCount]);
const handleScroll = React.useCallback(() => {
const newRange = calculateVisibleRange();
if (newRange) {
setStartChunk(newRange.start);
setEndChunk(newRange.end);
}
}, [calculateVisibleRange]);
React.useEffect(() => {
const container = parentRef?.current;
if (!container) {
return undefined;
}
const throttledHandleScroll = throttle(handleScroll, THROTTLE_DELAY, {
leading: true,
trailing: true,
});
container.addEventListener('scroll', throttledHandleScroll);
return () => {
container.removeEventListener('scroll', throttledHandleScroll);
throttledHandleScroll.cancel();
};
}, [handleScroll, parentRef]);
return React.useMemo(() => {
// boolean array that represents active chunks
const activeChunks = Array(chunksCount).fill(false);
for (let i = startChunk; i <= endChunk; i++) {
activeChunks[i] = true;
}
return activeChunks;
}, [chunksCount, startChunk, endChunk]);
};