Skip to content

Commit

Permalink
Prerender items longer for scroll direction
Browse files Browse the repository at this point in the history
  • Loading branch information
inokawa committed Sep 21, 2023
1 parent a781990 commit e2727e7
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Total",
"path": "lib/index.mjs",
"import": "*",
"limit": "5.20 kB"
"limit": "5.25 kB"
},
{
"name": "VList",
Expand Down
3 changes: 2 additions & 1 deletion src/core/scroller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ACTION_SCROLL_END,
UPDATE_SIZE,
ACTION_MANUAL_SCROLL,
SCROLL_IDLE,
} from "./store";
import { ScrollToIndexAlign } from "./types";
import { debounce, throttle, timeout, clamp } from "./utils";
Expand All @@ -19,7 +20,7 @@ const createOnWheel = (
onScrollStopped: () => void
) => {
return throttle((e: WheelEvent) => {
if (!store._getIsScrolling()) {
if (store._getScrollDirection() === SCROLL_IDLE) {
// Scroll start should be detected with scroll event
return;
}
Expand Down
20 changes: 8 additions & 12 deletions src/core/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const SUBPIXEL_THRESHOLD = 1.5; // 0.5 * 3
export const SCROLL_IDLE = 0;
export const SCROLL_DOWN = 1;
export const SCROLL_UP = 2;
type ScrollDirection =
export type ScrollDirection =
| typeof SCROLL_IDLE
| typeof SCROLL_DOWN
| typeof SCROLL_UP;
Expand Down Expand Up @@ -69,7 +69,7 @@ type Subscriber = () => void;
export const UPDATE_SCROLL = 0b00001;
export const UPDATE_SIZE = 0b00010;
export const UPDATE_JUMP = 0b00100;
export const UPDATE_IS_SCROLLING = 0b01000;
export const UPDATE_SCROLL_DIRECTION = 0b01000;
export const UPDATE_SCROLL_WITH_EVENT = 0b10000;

export type VirtualStore = {
Expand All @@ -82,7 +82,7 @@ export type VirtualStore = {
_getItemsLength(): number;
_getScrollOffset(): number;
_getScrollOffsetMax(): number;
_getIsScrolling(): boolean;
_getScrollDirection(): ScrollDirection;
_getViewportSize(): number;
_getCorrectedScrollSize(): number;
_getJumpCount(): number;
Expand Down Expand Up @@ -122,12 +122,8 @@ export const createVirtualStore = (
const updateScrollDirection = (dir: ScrollDirection): boolean => {
const prev = _scrollDirection;
_scrollDirection = dir;

// Return true if scrolling is just started or stopped
return (
_scrollDirection !== prev &&
(_scrollDirection === SCROLL_IDLE || prev === SCROLL_IDLE)
);
return _scrollDirection !== prev;
};

return {
Expand Down Expand Up @@ -183,8 +179,8 @@ export const createVirtualStore = (
return scrollOffset;
},
_getScrollOffsetMax: getScrollOffsetMax,
_getIsScrolling() {
return _scrollDirection !== SCROLL_IDLE;
_getScrollDirection() {
return _scrollDirection;
},
_getViewportSize() {
return viewportSize;
Expand Down Expand Up @@ -325,7 +321,7 @@ export const createVirtualStore = (
!_isManualScrolling
) {
if (updateScrollDirection(delta < 0 ? SCROLL_UP : SCROLL_DOWN)) {
mutated += UPDATE_IS_SCROLLING;
mutated += UPDATE_SCROLL_DIRECTION;
}
}

Expand All @@ -348,7 +344,7 @@ export const createVirtualStore = (
}
case ACTION_SCROLL_END: {
if (updateScrollDirection(SCROLL_IDLE)) {
mutated = UPDATE_IS_SCROLLING;
mutated = UPDATE_SCROLL_DIRECTION;
}
_isShifting = _isManualScrolling = false;
break;
Expand Down
55 changes: 41 additions & 14 deletions src/react/VGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@ import {
} from "react";
import {
ACTION_ITEMS_LENGTH_CHANGE,
UPDATE_IS_SCROLLING,
UPDATE_SCROLL_DIRECTION,
UPDATE_JUMP,
UPDATE_SCROLL,
UPDATE_SIZE,
VirtualStore,
createVirtualStore,
SCROLL_IDLE,
} from "../core/store";
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
import { useSelector } from "./useSelector";
import { max, min, values } from "../core/utils";
import { values } from "../core/utils";
import { createScroller } from "../core/scroller";
import { emptyComponents, refKey } from "./utils";
import {
clampEndIndex,
clampStartIndex,
emptyComponents,
refKey,
} from "./utils";
import { useStatic } from "./useStatic";
import {
CustomViewportComponent,
Expand Down Expand Up @@ -315,15 +321,15 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
hStore._getRange,
UPDATE_SCROLL + UPDATE_SIZE
);
const verticalScrolling = useSelector(
const vScrollDirection = useSelector(
vStore,
vStore._getIsScrolling,
UPDATE_IS_SCROLLING
vStore._getScrollDirection,
UPDATE_SCROLL_DIRECTION
);
const horizontalScrolling = useSelector(
const hScrollDirection = useSelector(
hStore,
hStore._getIsScrolling,
UPDATE_IS_SCROLLING
hStore._getScrollDirection,
UPDATE_SCROLL_DIRECTION
);
const vJumpCount = useSelector(vStore, vStore._getJumpCount, UPDATE_JUMP);
const hJumpCount = useSelector(hStore, hStore._getJumpCount, UPDATE_JUMP);
Expand All @@ -340,6 +346,8 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
true
);
const rootRef = useRef<HTMLDivElement>(null);
const vScrolling = vScrollDirection !== SCROLL_IDLE;
const hScrolling = hScrollDirection !== SCROLL_IDLE;

useIsomorphicLayoutEffect(() => {
const root = rootRef[refKey]!;
Expand Down Expand Up @@ -413,10 +421,29 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
};
}, [children]);

const overscanedStartRowIndex = max(startRowIndex - overscan, 0);
const overscanedEndRowIndex = min(endRowIndex + overscan, rowCount - 1);
const overscanedStartColIndex = max(startColIndex - overscan, 0);
const overscanedEndColIndex = min(endColIndex + overscan, colCount - 1);
const overscanedStartRowIndex = clampStartIndex(
startRowIndex,
overscan,
vScrollDirection
);
const overscanedEndRowIndex = clampEndIndex(
endRowIndex,
overscan,
vScrollDirection,
rowCount
);
const overscanedStartColIndex = clampStartIndex(
startColIndex,
overscan,
hScrollDirection
);
const overscanedEndColIndex = clampEndIndex(
endColIndex,
overscan,
hScrollDirection,
colCount
);

const items = useMemo(() => {
const res: ReactElement[] = [];
for (let i = overscanedStartRowIndex; i <= overscanedEndRowIndex; i++) {
Expand Down Expand Up @@ -451,7 +478,7 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
ref={rootRef}
width={width}
height={height}
scrolling={verticalScrolling || horizontalScrolling}
scrolling={vScrolling || hScrolling}
attrs={useMemo(
() => ({
...viewportAttrs,
Expand Down
34 changes: 26 additions & 8 deletions src/react/VList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,22 @@ import {
createVirtualStore,
UPDATE_SIZE,
UPDATE_JUMP,
UPDATE_IS_SCROLLING,
UPDATE_SCROLL_DIRECTION,
UPDATE_SCROLL,
SCROLL_IDLE,
} from "../core/store";
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
import { useSelector } from "./useSelector";
import { exists, max, min, values } from "../core/utils";
import { exists, values } from "../core/utils";
import { createScroller } from "../core/scroller";
import { MayHaveKey, emptyComponents, flattenChildren, refKey } from "./utils";
import {
MayHaveKey,
clampEndIndex,
clampStartIndex,
emptyComponents,
flattenChildren,
refKey,
} from "./utils";
import { useStatic } from "./useStatic";
import { useLatestRef } from "./useLatestRef";
import { createResizer } from "../core/resizer";
Expand Down Expand Up @@ -227,10 +235,10 @@ export const VList = forwardRef<VListHandle, VListProps>(
store._getRange,
UPDATE_SCROLL + UPDATE_SIZE
);
const scrolling = useSelector(
const scrollDirection = useSelector(
store,
store._getIsScrolling,
UPDATE_IS_SCROLLING
store._getScrollDirection,
UPDATE_SCROLL_DIRECTION
);
const jumpCount = useSelector(store, store._getJumpCount, UPDATE_JUMP);
const scrollSize = useSelector(
Expand All @@ -240,6 +248,7 @@ export const VList = forwardRef<VListHandle, VListProps>(
true
);
const rootRef = useRef<HTMLDivElement>(null);
const scrolling = scrollDirection !== SCROLL_IDLE;

useIsomorphicLayoutEffect(() => {
const root = rootRef[refKey]!;
Expand Down Expand Up @@ -298,8 +307,17 @@ export const VList = forwardRef<VListHandle, VListProps>(
[]

Check warning on line 307 in src/react/VList.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook useImperativeHandle has missing dependencies: 'scroller._scrollBy', 'scroller._scrollTo', 'scroller._scrollToIndex', and 'store'. Either include them or remove the dependency array
);

const overscanedStartIndex = max(startIndex - overscan, 0);
const overscanedEndIndex = min(endIndex + overscan, count - 1);
const overscanedStartIndex = clampStartIndex(
startIndex,
overscan,
scrollDirection
);
const overscanedEndIndex = clampEndIndex(
endIndex,
overscan,
scrollDirection,
count
);
const items = useMemo(() => {
const res: ReactElement[] = [];
for (let i = overscanedStartIndex; i <= overscanedEndIndex; i++) {
Expand Down
34 changes: 26 additions & 8 deletions src/react/WVList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ import {
} from "react";
import {
ACTION_ITEMS_LENGTH_CHANGE,
UPDATE_IS_SCROLLING,
UPDATE_SCROLL_DIRECTION,
UPDATE_JUMP,
UPDATE_SCROLL,
UPDATE_SIZE,
createVirtualStore,
SCROLL_IDLE,
} from "../core/store";
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
import { useSelector } from "./useSelector";
import { exists, max, min, values } from "../core/utils";
import { exists, values } from "../core/utils";
import { createWindowScroller } from "../core/scroller";
import { MayHaveKey, emptyComponents, flattenChildren, refKey } from "./utils";
import {
MayHaveKey,
clampEndIndex,
clampStartIndex,
emptyComponents,
flattenChildren,
refKey,
} from "./utils";
import { useStatic } from "./useStatic";
import { useLatestRef } from "./useLatestRef";
import { createWindowResizer } from "../core/resizer";
Expand Down Expand Up @@ -173,10 +181,10 @@ export const WVList = forwardRef<WVListHandle, WVListProps>(
store._getRange,
UPDATE_SCROLL + UPDATE_SIZE
);
const scrolling = useSelector(
const scrollDirection = useSelector(
store,
store._getIsScrolling,
UPDATE_IS_SCROLLING
store._getScrollDirection,
UPDATE_SCROLL_DIRECTION
);
const jumpCount = useSelector(store, store._getJumpCount, UPDATE_JUMP);
const scrollSize = useSelector(
Expand All @@ -186,6 +194,7 @@ export const WVList = forwardRef<WVListHandle, WVListProps>(
true
);
const rootRef = useRef<HTMLDivElement>(null);
const scrolling = scrollDirection !== SCROLL_IDLE;

useIsomorphicLayoutEffect(() => {
const root = rootRef[refKey]!;
Expand Down Expand Up @@ -228,8 +237,17 @@ export const WVList = forwardRef<WVListHandle, WVListProps>(
[]
);

const overscanedStartIndex = max(startIndex - overscan, 0);
const overscanedEndIndex = min(endIndex + overscan, count - 1);
const overscanedStartIndex = clampStartIndex(
startIndex,
overscan,
scrollDirection
);
const overscanedEndIndex = clampEndIndex(
endIndex,
overscan,
scrollDirection,
count
);
const items = useMemo(() => {
const res: ReactElement[] = [];
for (let i = overscanedStartIndex; i <= overscanedEndIndex; i++) {
Expand Down
26 changes: 25 additions & 1 deletion src/react/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactElement, ReactFragment, ReactNode } from "react";
import { exists, isArray } from "../core/utils";
import { exists, max, min, isArray } from "../core/utils";
import { SCROLL_DOWN, SCROLL_UP, ScrollDirection } from "../core/store";

export const refKey = "current";

Expand Down Expand Up @@ -35,3 +36,26 @@ export const flattenChildren = (children: ReactNode): ItemElement[] => {
};

export type MayHaveKey = { key?: React.Key };

export const clampStartIndex = (
startIndex: number,
overscan: number,
scrollDirection: ScrollDirection
): number => {
return max(
startIndex - (scrollDirection === SCROLL_DOWN ? 1 : max(1, overscan)),
0
);
};

export const clampEndIndex = (
endIndex: number,
overscan: number,
scrollDirection: ScrollDirection,
count: number
): number => {
return min(
endIndex + (scrollDirection === SCROLL_UP ? 1 : max(1, overscan)),
count - 1
);
};

0 comments on commit e2727e7

Please sign in to comment.