Last active
October 3, 2019 12:14
-
-
Save superandrew213/22aca60ff7740eadc481b5d9a4eebdca to your computer and use it in GitHub Desktop.
react-window FixedSizeList & FixedSizeGrid position tracker - Can be used for onTopReached, onEndReached, onItemsVisibilityChange
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import { Component } from 'react' | |
export type PositionsType = { | |
visibleColumnStopIndex: number, | |
visibleRowStartIndex: number, | |
visibleRowStopIndex: number, | |
visibleStartIndex?: number, | |
visibleStopIndex?: number, | |
} | |
export type ChangedVisibilityType = { | |
itemsChangedToHidden: Array<ChangedVisibilityItemType>, | |
itemsChangedToVisible: Array<ChangedVisibilityItemType>, | |
} | |
export type ChangedVisibilityItemType = { | |
index: number, | |
item: any, | |
} | |
export type Props = { | |
items: Array<any>, | |
children: (props: Object) => React$Node, | |
onTopReached?: () => void, | |
onEndReached?: () => void, | |
onItemsVisibilityChange?: (data: ChangedVisibilityType) => void, | |
} | |
class PositionTracker extends Component<Props> { | |
_lastPositions: PositionsType | |
_lastPositions = null | |
render() { | |
return this.props.children({ | |
onItemsRendered: this._onItemsRendered, | |
}) | |
} | |
_onItemsRendered = (positionsData: PositionsType): void => { | |
const { items, onTopReached, onEndReached, onItemsVisibilityChange } = this.props | |
const positions = this._getPositions(positionsData) | |
const { visibleRowStartIndex, visibleRowStopIndex } = positions | |
const scrollDirection = this._getScrollDirection(positions) | |
const columnCount = this._getColumnCount(positions) | |
const lastRowIndex = Math.ceil(items.length / columnCount) - 1 | |
// Reached top | |
if ( | |
onTopReached && | |
visibleRowStartIndex === 0 && | |
scrollDirection === 'BACKWARD' && | |
this._didVisibleRowStartIndexChange(positions) | |
) { | |
onTopReached() | |
} | |
// Reached bottom | |
if ( | |
onEndReached && | |
visibleRowStopIndex === lastRowIndex && | |
scrollDirection === 'FORWARD' && | |
this._didVisibleRowStopIndexChange(positions) | |
) { | |
onEndReached() | |
} | |
// Items that changed visibility | |
if (onItemsVisibilityChange) { | |
const itemsThatChangedVisibility = this._getItemsThatChangedVisibility(positions) | |
if (itemsThatChangedVisibility) { | |
onItemsVisibilityChange(itemsThatChangedVisibility) | |
} | |
} | |
// Store last positions | |
this._lastPositions = positions | |
} | |
// Support FixedSizeList & FixedSizeGrid | |
_getPositions = (positionsData: PositionsType): PositionsType => ({ | |
visibleColumnStopIndex: positionsData.visibleColumnStopIndex, | |
visibleRowStartIndex: positionsData.visibleStartIndex != null ? ( | |
positionsData.visibleStartIndex | |
) : ( | |
positionsData.visibleRowStartIndex | |
), | |
visibleRowStopIndex: positionsData.visibleStopIndex != null ? ( | |
positionsData.visibleStopIndex | |
) : ( | |
positionsData.visibleRowStopIndex | |
), | |
}) | |
_getColumnCount = (positions: PositionsType): number => { | |
return positions.visibleColumnStopIndex ? positions.visibleColumnStopIndex + 1 : 1 | |
} | |
_getScrollDirection = (positions: PositionsType) => { | |
const { | |
visibleRowStartIndex, | |
visibleRowStopIndex, | |
} = positions | |
if (this._lastPositions == null) { | |
return | |
} | |
if ( | |
this._lastPositions.visibleRowStartIndex > visibleRowStartIndex || | |
this._lastPositions.visibleRowStopIndex > visibleRowStopIndex | |
) { | |
return 'BACKWARD' | |
} | |
if ( | |
this._lastPositions.visibleRowStartIndex < visibleRowStartIndex || | |
this._lastPositions.visibleRowStopIndex < visibleRowStopIndex | |
) { | |
return 'FORWARD' | |
} | |
} | |
_getItemsThatChangedVisibility = (positions: PositionsType): ChangedVisibilityType | void => { | |
const { items } = this.props | |
const { | |
visibleRowStartIndex, | |
visibleRowStopIndex, | |
} = positions | |
const scrollDirection = this._getScrollDirection(positions) | |
// Ensure scroll has started | |
if (!scrollDirection) { | |
return | |
} | |
const columnCount = this._getColumnCount(positions) | |
const itemsChangedToHidden = [] | |
const itemsChangedToVisible = [] | |
const visibleRowStartIndexChanged = this._didVisibleRowStartIndexChange(positions) | |
const visibleRowStopIndexChanged = this._didVisibleRowStopIndexChange(positions) | |
// Hidden | |
const hiddenItemsStartIndex = scrollDirection === 'FORWARD' ? ( | |
visibleRowStartIndexChanged ? (visibleRowStartIndex - 1) * columnCount : null | |
) : ( | |
visibleRowStopIndexChanged ? (visibleRowStopIndex + 1) * columnCount : null | |
) | |
// Visible | |
const visibleItemsStartIndex = scrollDirection === 'FORWARD' ? ( | |
visibleRowStopIndexChanged ? visibleRowStopIndex * columnCount : null | |
) : ( | |
visibleRowStartIndexChanged ? visibleRowStartIndex * columnCount : null | |
) | |
for (let i = 0; i < columnCount; i += 1) { | |
if (hiddenItemsStartIndex != null) { | |
itemsChangedToHidden.push({ | |
index: hiddenItemsStartIndex + i, | |
item: items[hiddenItemsStartIndex + i], | |
}) | |
} | |
if (visibleItemsStartIndex != null) { | |
itemsChangedToVisible.push({ | |
index: visibleItemsStartIndex + i, | |
item: items[visibleItemsStartIndex + i], | |
}) | |
} | |
} | |
return { | |
itemsChangedToHidden, | |
itemsChangedToVisible, | |
} | |
} | |
_didVisibleRowStartIndexChange = (positions: PositionsType): boolean => { | |
return ( | |
this._lastPositions != null && | |
this._lastPositions.visibleRowStartIndex !== positions.visibleRowStartIndex | |
) | |
} | |
_didVisibleRowStopIndexChange = (positions: PositionsType): boolean => { | |
return ( | |
this._lastPositions != null && | |
this._lastPositions.visibleRowStopIndex !== positions.visibleRowStopIndex | |
) | |
} | |
} | |
export default PositionTracker |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Using it for grids, but should work for lists too.
and