Skip to content

Commit

Permalink
Merge pull request #401 from inokawa/improve-offset-clamp
Browse files Browse the repository at this point in the history
Suppress rerendering of WindowVirtualizer outside of viewport
  • Loading branch information
inokawa authored Mar 14, 2024
2 parents d64d874 + 432f8e2 commit 4272512
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 50 deletions.
41 changes: 21 additions & 20 deletions src/core/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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 [
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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: {
Expand Down
8 changes: 1 addition & 7 deletions src/react/Virtualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -176,7 +172,6 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
horizontal: horizontalProp,
cache,
startMargin,
endMargin,
ssrCount,
as: Element = "div",
item: ItemElement = "div",
Expand Down Expand Up @@ -206,8 +201,7 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
ssrCount,
cache,
!itemSize,
startMargin,
endMargin
startMargin
);
return [
_store,
Expand Down
7 changes: 1 addition & 6 deletions src/vue/Virtualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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);
Expand Down
18 changes: 4 additions & 14 deletions stories/react/basics/Virtualizer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const createRows = (num: number) => {
export const HeaderAndFooter: StoryObj = {
render: () => {
const headerHeight = 400;
const footerHeight = 600;
return (
<div
style={{
Expand All @@ -53,12 +52,8 @@ export const HeaderAndFooter: StoryObj = {
<div style={{ backgroundColor: "burlywood", height: headerHeight }}>
header
</div>
<Virtualizer startMargin={headerHeight} endMargin={footerHeight}>
{createRows(1000)}
</Virtualizer>
<div style={{ backgroundColor: "steelblue", height: footerHeight }}>
footer
</div>
<Virtualizer startMargin={headerHeight}>{createRows(1000)}</Virtualizer>
<div style={{ backgroundColor: "steelblue", height: 600 }}>footer</div>
</div>
);
},
Expand All @@ -67,7 +62,6 @@ export const HeaderAndFooter: StoryObj = {
export const StickyHeaderAndFooter: StoryObj = {
render: () => {
const headerHeight = 40;
const footerHeight = 60;
return (
<div
style={{
Expand All @@ -89,14 +83,12 @@ export const StickyHeaderAndFooter: StoryObj = {
>
header
</div>
<Virtualizer startMargin={headerHeight} endMargin={footerHeight}>
{createRows(1000)}
</Virtualizer>
<Virtualizer startMargin={headerHeight}>{createRows(1000)}</Virtualizer>
<div
style={{
position: "sticky",
backgroundColor: "steelblue",
height: footerHeight,
height: 60,
bottom: 0,
}}
>
Expand Down Expand Up @@ -128,7 +120,6 @@ export const Nested: StoryObj = {
<Virtualizer
scrollRef={ref}
startMargin={outerPadding + innerPadding}
endMargin={outerPadding + innerPadding}
>
{createRows(1000)}
</Virtualizer>
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 2 additions & 3 deletions stories/vue/HeaderAndFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const data = Array.from({ length: 1000 }).map((_, i) => createItem(i));
const headerHeight = 400;
const footerHeight = 600;
</script>

<template>
Expand All @@ -22,12 +21,12 @@ const footerHeight = 600;
<div :style="{ backgroundColor: 'burlywood', height: headerHeight + 'px' }">
header
</div>
<Virtualizer :data="data" #default="item" :startMargin="headerHeight" :endMargin="footerHeight">
<Virtualizer :data="data" #default="item" :startMargin="headerHeight">
<div :key="item.index" :style="{ height: item.size, background: 'white', borderBottom: 'solid 1px #ccc' }">
{{ item.index }}
</div>
</Virtualizer>
<div :style="{ backgroundColor: 'steelblue', height: footerHeight + 'px' }">
<div :style="{ backgroundColor: 'steelblue', height: '600px' }">
footer
</div>
</div>
Expand Down

0 comments on commit 4272512

Please sign in to comment.