diff --git a/src/core/store.ts b/src/core/store.ts index 327bbe45b..3456ffcb6 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -17,7 +17,7 @@ import type { ItemResize, ItemsRange, } from "./types"; -import { abs, clamp, max, min } from "./utils"; +import { abs, max, min } from "./utils"; /** @internal */ export const SCROLL_IDLE = 0; @@ -63,7 +63,7 @@ type Actions = | [type: typeof ACTION_VIEWPORT_RESIZE, size: number] | [ type: typeof ACTION_ITEMS_LENGTH_CHANGE, - arg: [length: number, isShift?: boolean | undefined] + arg: [length: number, isShift?: boolean | undefined], ] | [type: typeof ACTION_START_OFFSET_CHANGE, offset: number] | [type: typeof ACTION_MANUAL_SCROLL, dummy?: void] @@ -140,8 +140,7 @@ export const createVirtualStore = ( ssrCount: number = 0, cacheSnapshot?: CacheSnapshot | undefined, shouldAutoEstimateItemSize: boolean = false, - startSpacerSize: number = 0, - endSpacerSize: number = 0 + startSpacerSize: number = 0 ): VirtualStore => { let isSSR = !!ssrCount; let stateVersion: StateVersion = []; @@ -201,7 +200,9 @@ export const createVirtualStore = ( if (_flushedJump) { return _prevRange; } - _prevRange = getRange(getRelativeScrollOffset() + pendingJump + jump); + _prevRange = getRange( + max(0, getRelativeScrollOffset() + pendingJump + jump) + ); if (_frozenRange) { return [ @@ -267,20 +268,10 @@ export const createVirtualStore = ( switch (type) { case ACTION_SCROLL: { - // Scroll offset may exceed min or max especially in Safari's elastic scrolling. - const nextScrollOffset = clamp( - payload, - 0, - getTotalSize() + startSpacerSize + endSpacerSize - ); const flushedJump = _flushedJump; _flushedJump = 0; - // Skip if offset is not changed - if (nextScrollOffset === scrollOffset) { - break; - } - const delta = nextScrollOffset - scrollOffset; + const delta = payload - scrollOffset; const distance = abs(delta); // Scroll event after jump compensation is not reliable because it may result in the opposite direction. @@ -314,11 +305,21 @@ export const createVirtualStore = ( isSSR = false; } - // Update synchronously if scrolled a lot - shouldSync = distance > viewportSize; + scrollOffset = payload; + mutated = UPDATE_SCROLL_EVENT; + + // Skip if offset is not changed + // Scroll offset may exceed min or max especially in Safari's elastic scrolling. + const relativeOffset = getRelativeScrollOffset(); + if ( + relativeOffset >= -viewportSize && + relativeOffset <= getTotalSize() + ) { + mutated += UPDATE_VIRTUAL_STATE; - scrollOffset = nextScrollOffset; - mutated = UPDATE_VIRTUAL_STATE + UPDATE_SCROLL_EVENT; + // Update synchronously if scrolled a lot + shouldSync = distance > viewportSize; + } break; } case ACTION_SCROLL_END: { diff --git a/src/react/Virtualizer.tsx b/src/react/Virtualizer.tsx index beebf6145..75bc75829 100644 --- a/src/react/Virtualizer.tsx +++ b/src/react/Virtualizer.tsx @@ -116,10 +116,6 @@ export interface VirtualizerProps { * If you put an element before virtualizer, you have to define its height with this prop. */ startMargin?: number; - /** - * If you put an element after virtualizer, you have to define its height with this prop. - */ - endMargin?: number; /** * A prop for SSR. If set, the specified amount of items will be mounted in the initial rendering regardless of the container size until hydrated. */ @@ -176,7 +172,6 @@ export const Virtualizer = forwardRef( horizontal: horizontalProp, cache, startMargin, - endMargin, ssrCount, as: Element = "div", item: ItemElement = "div", @@ -206,8 +201,7 @@ export const Virtualizer = forwardRef( ssrCount, cache, !itemSize, - startMargin, - endMargin + startMargin ); return [ _store, diff --git a/src/vue/Virtualizer.tsx b/src/vue/Virtualizer.tsx index ebe04827a..0ff3bd72f 100644 --- a/src/vue/Virtualizer.tsx +++ b/src/vue/Virtualizer.tsx @@ -92,10 +92,6 @@ const props = { * If you put an element before virtualizer, you have to define its height with this prop. */ startMargin: Number, - /** - * If you put an element after virtualizer, you have to define its height with this prop. - */ - endMargin: Number, /** * A prop for SSR. If set, the specified amount of items will be mounted in the initial rendering regardless of the container size until hydrated. */ @@ -116,8 +112,7 @@ export const Virtualizer = /*#__PURE__*/ defineComponent({ props.ssrCount, undefined, !props.itemSize, - props.startMargin, - props.endMargin + props.startMargin ); const resizer = createResizer(store, isHorizontal); const scroller = createScroller(store, isHorizontal); diff --git a/stories/react/basics/Virtualizer.stories.tsx b/stories/react/basics/Virtualizer.stories.tsx index fa2a8316c..fb4a037cf 100644 --- a/stories/react/basics/Virtualizer.stories.tsx +++ b/stories/react/basics/Virtualizer.stories.tsx @@ -39,7 +39,6 @@ const createRows = (num: number) => { export const HeaderAndFooter: StoryObj = { render: () => { const headerHeight = 400; - const footerHeight = 600; return (
header
- - {createRows(1000)} - -
- footer -
+ {createRows(1000)} +
footer
); }, @@ -67,7 +62,6 @@ export const HeaderAndFooter: StoryObj = { export const StickyHeaderAndFooter: StoryObj = { render: () => { const headerHeight = 40; - const footerHeight = 60; return (
header
- - {createRows(1000)} - + {createRows(1000)}
@@ -128,7 +120,6 @@ export const Nested: StoryObj = { {createRows(1000)} @@ -207,7 +198,6 @@ export const BiDirectionalInfiniteScrolling: StoryObj = { ref={ref} shift={shifting ? true : false} startMargin={spinnerHeight} - endMargin={spinnerHeight} onRangeChange={async (start, end) => { if (!ready.current) return; if (end + THRESHOLD > count && endFetchedCountRef.current < count) { diff --git a/stories/vue/HeaderAndFooter.vue b/stories/vue/HeaderAndFooter.vue index 53ab92657..2e53543c2 100644 --- a/stories/vue/HeaderAndFooter.vue +++ b/stories/vue/HeaderAndFooter.vue @@ -8,7 +8,6 @@ const data = Array.from({ length: 1000 }).map((_, i) => createItem(i)); const headerHeight = 400; -const footerHeight = 600;