Files
AFFiNE-Mirror/packages/frontend/component/src/ui/masonry/utils.ts
T
CatsJuice a53e231bad feat(component, mobile): masonry layout with virtual scroll support, adapted with all docs (#9208)
### Preview

![CleanShot 2024-12-19 at 20.41.47.gif](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/60a701ea-bca0-42d5-8a06-f10af44c8fc8.gif)

### Render when scrolling

![CleanShot 2024-12-20 at 09.54.26.gif](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/df0008d7-5bd9-4e98-b426-cb1036dbb611.gif)

### api
```tsx
const items = useMemo(() => {
    return {
        id: '',
        height: 100,
        children: <div></div>
    }
}, [])

<Masonry items={items} />
```
2024-12-20 05:32:17 +00:00

88 lines
2.2 KiB
TypeScript

import type { MasonryItem, MasonryItemXYWH } from './type';
export const calcColumns = (
totalWidth: number,
itemWidth: number | 'stretch',
itemWidthMin: number,
gapX: number,
paddingX: number
) => {
const availableWidth = totalWidth - paddingX * 2;
if (itemWidth === 'stretch') {
let columns = 1;
while (columns * itemWidthMin + (columns - 1) * gapX < availableWidth) {
columns++;
}
const finalColumns = columns - 1;
const finalWidth =
(availableWidth - (finalColumns - 1) * gapX) / finalColumns;
return {
columns: finalColumns,
width: finalWidth,
};
} else {
let columns = 1;
while (columns * itemWidth + (columns - 1) * gapX < availableWidth) {
columns++;
}
return {
columns: columns - 1,
width: itemWidth,
};
}
};
export const calcLayout = (
items: MasonryItem[],
options: {
columns: number;
width: number;
gapX: number;
gapY: number;
paddingX: number;
paddingY: number;
}
) => {
const { columns, width, gapX, gapY, paddingX, paddingY } = options;
const layoutMap = new Map<MasonryItem['id'], MasonryItemXYWH>();
const heightStack = Array.from({ length: columns }, () => paddingY);
items.forEach(item => {
const minHeight = Math.min(...heightStack);
const minHeightIndex = heightStack.indexOf(minHeight);
const x = minHeightIndex * (width + gapX) + paddingX;
const y = minHeight + gapY;
heightStack[minHeightIndex] = y + item.height;
layoutMap.set(item.id, { x, y, w: width, h: item.height });
});
const finalHeight = Math.max(...heightStack) + paddingY;
return { layout: layoutMap, height: finalHeight };
};
export const calcSleep = (options: {
viewportHeight: number;
scrollY: number;
layoutMap: Map<MasonryItem['id'], MasonryItemXYWH>;
preloadHeight: number;
}) => {
const { viewportHeight, scrollY, layoutMap, preloadHeight } = options;
const sleepMap = new Map<MasonryItem['id'], boolean>();
layoutMap.forEach((layout, id) => {
const { y, h } = layout;
const isInView =
y + h + preloadHeight > scrollY &&
y - preloadHeight < scrollY + viewportHeight;
sleepMap.set(id, !isInView);
});
return sleepMap;
};