Improve virtual grid performance

This commit is contained in:
jeffvli
2022-12-09 15:58:30 -08:00
parent 9f3c6d3029
commit e32ade3b54
6 changed files with 91 additions and 81 deletions
@@ -7,7 +7,7 @@ import { SimpleImg } from 'react-simple-img';
import type { ListChildComponentProps } from 'react-window'; import type { ListChildComponentProps } from 'react-window';
import styled from 'styled-components'; import styled from 'styled-components';
import { Text } from '/@/components/text'; import { Text } from '/@/components/text';
import type { PlayQueueAddOptions, LibraryItem, CardRow, CardRoute, Play } from '/@/types'; import type { LibraryItem, CardRow, CardRoute, Play } from '/@/types';
import GridCardControls from './grid-card-controls'; import GridCardControls from './grid-card-controls';
const CardWrapper = styled.div<{ const CardWrapper = styled.div<{
@@ -113,7 +113,6 @@ interface BaseGridCardProps {
controls: { controls: {
cardControls: any[]; cardControls: any[];
cardRows: CardRow[]; cardRows: CardRow[];
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem; itemType: LibraryItem;
playButtonBehavior: Play; playButtonBehavior: Play;
route: CardRoute; route: CardRoute;
@@ -135,7 +134,7 @@ export const DefaultCard = ({
sizes, sizes,
}: BaseGridCardProps) => { }: BaseGridCardProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { index, isScrolling } = listChildProps; const { index } = listChildProps;
const { itemGap, itemHeight, itemWidth } = sizes; const { itemGap, itemHeight, itemWidth } = sizes;
const { itemType, cardRows, route } = controls; const { itemType, cardRows, route } = controls;
@@ -190,12 +189,10 @@ export const DefaultCard = ({
</Center> </Center>
)} )}
<ControlsContainer> <ControlsContainer>
{!isScrolling && ( <GridCardControls
<GridCardControls itemData={data}
itemData={data} itemType={itemType}
itemType={itemType} />
/>
)}
</ControlsContainer> </ControlsContainer>
</ImageSection> </ImageSection>
<DetailSection> <DetailSection>
@@ -42,9 +42,18 @@ const PlayButton = styled.button<PlayButtonType>`
`; `;
const SecondaryButton = styled(_Button)` const SecondaryButton = styled(_Button)`
fill: white !important; opacity: 0.8;
svg: { transition: opacity 0.2s ease-in-out;
fill: white !important; transition: scale 0.2s linear;
&:hover {
opacity: 1;
scale: 1.1;
}
&:active {
opacity: 1;
scale: 1;
} }
`; `;
@@ -132,13 +141,7 @@ export const GridCardControls = ({
{/* <TopControls /> */} {/* <TopControls /> */}
{/* <CenterControls /> */} {/* <CenterControls /> */}
<BottomControls> <BottomControls>
<PlayButton <PlayButton onClick={handlePlay}>
// initial="initial"
// variants={buttonVariants}
// whileHover="hover"
// whileTap="pressed"
onClick={handlePlay}
>
<RiPlayFill size={25} /> <RiPlayFill size={25} />
</PlayButton> </PlayButton>
<Group spacing="xs"> <Group spacing="xs">
@@ -166,6 +169,7 @@ export const GridCardControls = ({
<DropdownMenu.Target> <DropdownMenu.Target>
<SecondaryButton <SecondaryButton
p={5} p={5}
sx={{ svg: { fill: 'white !important' } }}
variant="subtle" variant="subtle"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@@ -1,11 +1,12 @@
import { useMemo } from 'react'; import { memo } from 'react';
import type { ListChildComponentProps } from 'react-window'; import type { ListChildComponentProps } from 'react-window';
import { areEqual } from 'react-window';
import { DefaultCard } from '/@/components/virtual-grid/grid-card/default-card'; import { DefaultCard } from '/@/components/virtual-grid/grid-card/default-card';
import { PosterCard } from '/@/components/virtual-grid/grid-card/poster-card'; import { PosterCard } from '/@/components/virtual-grid/grid-card/poster-card';
import type { GridCardData } from '/@/types'; import type { GridCardData } from '/@/types';
import { CardDisplayType } from '/@/types'; import { CardDisplayType } from '/@/types';
export const GridCard = ({ data, index, style, isScrolling }: ListChildComponentProps) => { export const GridCard = memo(({ data, index, style }: ListChildComponentProps) => {
const { const {
itemHeight, itemHeight,
itemWidth, itemWidth,
@@ -13,7 +14,6 @@ export const GridCard = ({ data, index, style, isScrolling }: ListChildComponent
itemGap, itemGap,
itemCount, itemCount,
cardControls, cardControls,
handlePlayQueueAdd,
cardRows, cardRows,
itemData, itemData,
itemType, itemType,
@@ -22,11 +22,8 @@ export const GridCard = ({ data, index, style, isScrolling }: ListChildComponent
display, display,
} = data as GridCardData; } = data as GridCardData;
const cards = []; const cards = [];
const startIndex = useMemo(() => index * columnCount, [columnCount, index]); const startIndex = index * columnCount;
const stopIndex = useMemo( const stopIndex = Math.min(itemCount - 1, startIndex + columnCount - 1);
() => Math.min(itemCount - 1, startIndex + columnCount - 1),
[columnCount, itemCount, startIndex],
);
const View = display === CardDisplayType.CARD ? DefaultCard : PosterCard; const View = display === CardDisplayType.CARD ? DefaultCard : PosterCard;
@@ -38,28 +35,29 @@ export const GridCard = ({ data, index, style, isScrolling }: ListChildComponent
controls={{ controls={{
cardControls, cardControls,
cardRows, cardRows,
handlePlayQueueAdd,
itemType, itemType,
playButtonBehavior, playButtonBehavior,
route, route,
}} }}
data={itemData[i]} data={itemData[i]}
listChildProps={{ index, isScrolling }} listChildProps={{ index }}
sizes={{ itemGap, itemHeight, itemWidth }} sizes={{ itemGap, itemHeight, itemWidth }}
/>, />,
); );
} }
return ( return (
<div <>
style={{ <div
...style, style={{
alignItems: 'center', ...style,
display: 'flex', alignItems: 'center',
justifyContent: 'start', display: 'flex',
}} justifyContent: 'start',
> }}
{cards} >
</div> {cards}
</div>
</>
); );
}; }, areEqual);
@@ -8,7 +8,7 @@ import type { ListChildComponentProps } from 'react-window';
import styled from 'styled-components'; import styled from 'styled-components';
import { Skeleton } from '/@/components/skeleton'; import { Skeleton } from '/@/components/skeleton';
import { Text } from '/@/components/text'; import { Text } from '/@/components/text';
import type { PlayQueueAddOptions, LibraryItem, CardRow, CardRoute, Play } from '/@/types'; import type { LibraryItem, CardRow, CardRoute, Play } from '/@/types';
import GridCardControls from './grid-card-controls'; import GridCardControls from './grid-card-controls';
const CardWrapper = styled.div<{ const CardWrapper = styled.div<{
@@ -117,7 +117,6 @@ interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
controls: { controls: {
cardRows: CardRow[]; cardRows: CardRow[];
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem; itemType: LibraryItem;
playButtonBehavior: Play; playButtonBehavior: Play;
route: CardRoute; route: CardRoute;
@@ -166,7 +165,6 @@ export const PosterCard = ({
height={sizes.itemWidth} height={sizes.itemWidth}
importance="auto" importance="auto"
placeholder={'var(--card-default-bg)'} placeholder={'var(--card-default-bg)'}
// placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
src={data?.imageUrl} src={data?.imageUrl}
width={sizes.itemWidth} width={sizes.itemWidth}
/> />
@@ -184,14 +182,12 @@ export const PosterCard = ({
/> />
</Center> </Center>
)} )}
{!listChildProps.isScrolling && ( <ControlsContainer>
<ControlsContainer> <GridCardControls
<GridCardControls itemData={data}
itemData={data} itemType={controls.itemType}
itemType={controls.itemType} />
/> </ControlsContainer>
</ControlsContainer>
)}
</ImageSection> </ImageSection>
</Link> </Link>
<DetailSection> <DetailSection>
@@ -1,11 +1,40 @@
import type { Ref } from 'react'; import type { Ref } from 'react';
import { useMemo } from 'react'; import debounce from 'lodash/debounce';
import memoize from 'memoize-one';
import type { FixedSizeListProps } from 'react-window'; import type { FixedSizeListProps } from 'react-window';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import styled from 'styled-components'; import styled from 'styled-components';
import { GridCard } from '/@/components/virtual-grid/grid-card'; import { GridCard } from '/@/components/virtual-grid/grid-card';
import type { CardRow, LibraryItem, CardDisplayType, CardRoute } from '/@/types'; import type { CardRow, LibraryItem, CardDisplayType, CardRoute } from '/@/types';
const createItemData = memoize(
(
cardRows,
columnCount,
display,
itemCount,
itemData,
itemGap,
itemHeight,
itemType,
itemWidth,
route,
) => ({
cardRows,
columnCount,
display,
itemCount,
itemData,
itemGap,
itemHeight,
itemType,
itemWidth,
route,
}),
);
const createScrollHandler = memoize((onScroll) => debounce(onScroll, 250));
export const VirtualGridWrapper = ({ export const VirtualGridWrapper = ({
refInstance, refInstance,
cardRows, cardRows,
@@ -35,44 +64,31 @@ export const VirtualGridWrapper = ({
route?: CardRoute; route?: CardRoute;
rowCount: number; rowCount: number;
}) => { }) => {
const memoizedItemData = useMemo( const memoizedItemData = createItemData(
() => ({ cardRows,
cardRows, columnCount,
columnCount, display,
display, itemCount,
itemCount, itemData,
itemData, itemGap,
itemGap, itemHeight,
itemHeight, itemType,
itemType, itemWidth,
itemWidth, route,
route,
}),
[
cardRows,
itemType,
columnCount,
itemCount,
itemData,
display,
itemGap,
itemHeight,
route,
itemWidth,
],
); );
const memoizedOnScroll = createScrollHandler(onScroll);
return ( return (
<FixedSizeList <FixedSizeList
ref={refInstance} ref={refInstance}
{...rest} {...rest}
useIsScrolling
initialScrollOffset={initialScrollOffset} initialScrollOffset={initialScrollOffset}
itemCount={rowCount} itemCount={rowCount}
itemData={memoizedItemData} itemData={memoizedItemData}
itemSize={itemHeight} itemSize={itemHeight}
overscanCount={5} overscanCount={5}
onScroll={onScroll} onScroll={memoizedOnScroll}
> >
{GridCard} {GridCard}
</FixedSizeList> </FixedSizeList>
@@ -86,7 +86,7 @@ export const VirtualInfiniteGrid = ({
[columnCount, fetchFn, itemData], [columnCount, fetchFn, itemData],
); );
const debouncedLoadMoreItems = debounce(loadMoreItems, 400); const debouncedLoadMoreItems = debounce(loadMoreItems, 500);
useEffect(() => { useEffect(() => {
if (loader.current) { if (loader.current) {
@@ -110,7 +110,6 @@ export const VirtualInfiniteGrid = ({
> >
{({ onItemsRendered, ref: infiniteLoaderRef }) => ( {({ onItemsRendered, ref: infiniteLoaderRef }) => (
<VirtualGridWrapper <VirtualGridWrapper
useIsScrolling
cardRows={cardRows} cardRows={cardRows}
columnCount={columnCount} columnCount={columnCount}
display={display || CardDisplayType.CARD} display={display || CardDisplayType.CARD}