mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-16 21:50:35 +02:00
Improve virtual grid performance
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user