@cleverbamboo/react-virtual-masonry 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +343 -0
- package/README.zh-CN.md +490 -0
- package/dist/DynamicMasonryView.d.ts +97 -0
- package/dist/DynamicMasonryView.d.ts.map +1 -0
- package/dist/FullWidthEqualHeightMasonry.d.ts +51 -0
- package/dist/FullWidthEqualHeightMasonry.d.ts.map +1 -0
- package/dist/VirtualMasonry.d.ts +35 -0
- package/dist/VirtualMasonry.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +674 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +680 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 [Your Name]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# React Virtual Masonry
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
A high-performance React virtual scrolling masonry layout library with support for waterfall and equal-height layouts.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **High-Performance Virtual Scrolling** - Only renders visible elements, supports massive datasets
|
|
10
|
+
- 📐 **Multiple Layout Modes**
|
|
11
|
+
- Waterfall Layout (Pinterest Style) - Variable width and height, automatically finds shortest column
|
|
12
|
+
- Equal Height Layout (Google Photos Style) - Same height per row, width adapts to original aspect ratio
|
|
13
|
+
- Dynamic Layout - Supports switching layout types based on API response
|
|
14
|
+
- 🎯 **Smart Preloading** - Infinite scroll based on IntersectionObserver with customizable preload distance
|
|
15
|
+
- 🎨 **Fully Customizable** - Custom render functions, loading states, spacing, and more
|
|
16
|
+
- 📱 **Responsive Design** - Automatically adapts to container width changes using ResizeObserver
|
|
17
|
+
- ⚡ **RAF Optimized** - Uses requestAnimationFrame for smooth scrolling performance
|
|
18
|
+
- 🔧 **TypeScript Support** - Complete TypeScript type definitions
|
|
19
|
+
- 🪶 **Zero Dependencies** - No external dependencies except React
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install react-virtual-masonry
|
|
25
|
+
# or
|
|
26
|
+
yarn add react-virtual-masonry
|
|
27
|
+
# or
|
|
28
|
+
pnpm add react-virtual-masonry
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 🎯 Quick Start
|
|
32
|
+
|
|
33
|
+
### 1. Waterfall Layout (Pinterest Style)
|
|
34
|
+
|
|
35
|
+
Perfect for image galleries, product listings, etc.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { VirtualMasonry } from 'react-virtual-masonry';
|
|
39
|
+
|
|
40
|
+
function ImageGallery() {
|
|
41
|
+
// Data loading function
|
|
42
|
+
const loadData = async (page: number, pageSize: number) => {
|
|
43
|
+
const response = await fetch(`/api/images?page=${page}&size=${pageSize}`);
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
return {
|
|
46
|
+
data: data.items, // Data array
|
|
47
|
+
hasMore: data.hasMore, // Whether there's more data
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<VirtualMasonry
|
|
53
|
+
loadData={loadData}
|
|
54
|
+
pageSize={30}
|
|
55
|
+
minColumnWidth={200}
|
|
56
|
+
maxColumnWidth={350}
|
|
57
|
+
gap={16}
|
|
58
|
+
renderItem={(item) => (
|
|
59
|
+
<div
|
|
60
|
+
style={{
|
|
61
|
+
position: 'absolute',
|
|
62
|
+
left: item.x,
|
|
63
|
+
top: item.y,
|
|
64
|
+
width: item.width,
|
|
65
|
+
height: item.height,
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<img src={item.url} alt={item.title} />
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Equal Height Layout (Google Photos Style)
|
|
77
|
+
|
|
78
|
+
Perfect for photo albums, media galleries, etc.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { FullWidthEqualHeightMasonry } from 'react-virtual-masonry';
|
|
82
|
+
|
|
83
|
+
function PhotoAlbum() {
|
|
84
|
+
const loadData = async (page: number, pageSize: number) => {
|
|
85
|
+
const response = await fetch(`/api/photos?page=${page}&size=${pageSize}`);
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
return {
|
|
88
|
+
data: data.items,
|
|
89
|
+
hasMore: data.hasMore,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<FullWidthEqualHeightMasonry
|
|
95
|
+
loadData={loadData}
|
|
96
|
+
pageSize={30}
|
|
97
|
+
targetRowHeight={245}
|
|
98
|
+
sizeRange={[230, 260]}
|
|
99
|
+
gap={8}
|
|
100
|
+
renderItem={(item) => (
|
|
101
|
+
<div
|
|
102
|
+
style={{
|
|
103
|
+
position: 'absolute',
|
|
104
|
+
left: item.x,
|
|
105
|
+
top: item.y,
|
|
106
|
+
width: item.width,
|
|
107
|
+
height: item.height,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<img src={item.url} alt={item.title} />
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. Dynamic Layout
|
|
119
|
+
|
|
120
|
+
Automatically switches between waterfall and equal-height layouts based on API response.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { DynamicMasonryView } from 'react-virtual-masonry';
|
|
124
|
+
|
|
125
|
+
function Gallery() {
|
|
126
|
+
const loadData = async (page: number, pageSize: number) => {
|
|
127
|
+
const response = await fetch(`/api/gallery?page=${page}&size=${pageSize}`);
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
|
|
130
|
+
// First request returns layout type
|
|
131
|
+
if (page === 1) {
|
|
132
|
+
return {
|
|
133
|
+
data: data.items,
|
|
134
|
+
hasMore: data.hasMore,
|
|
135
|
+
isMasonry: data.layoutType === 'waterfall', // true for waterfall, false for equal-height
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
data: data.items,
|
|
141
|
+
hasMore: data.hasMore,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<DynamicMasonryView
|
|
147
|
+
loadData={loadData}
|
|
148
|
+
pageSize={30}
|
|
149
|
+
waterfallConfig={{
|
|
150
|
+
minColumnWidth: 200,
|
|
151
|
+
maxColumnWidth: 350,
|
|
152
|
+
gap: 16,
|
|
153
|
+
}}
|
|
154
|
+
equalHeightConfig={{
|
|
155
|
+
targetRowHeight: 245,
|
|
156
|
+
sizeRange: [230, 260],
|
|
157
|
+
gap: 8,
|
|
158
|
+
}}
|
|
159
|
+
renderItem={(item, index, isMasonry) => (
|
|
160
|
+
<div
|
|
161
|
+
style={{
|
|
162
|
+
position: 'absolute',
|
|
163
|
+
left: item.x,
|
|
164
|
+
top: item.y,
|
|
165
|
+
width: item.width,
|
|
166
|
+
height: item.height,
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<img src={item.url} alt={item.title} />
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 📖 API Documentation
|
|
178
|
+
|
|
179
|
+
### VirtualMasonry (Waterfall Layout)
|
|
180
|
+
|
|
181
|
+
| Property | Type | Default | Description |
|
|
182
|
+
|----------|------|---------|-------------|
|
|
183
|
+
| `loadData` | `(page: number, pageSize: number) => Promise<{data: any[], hasMore: boolean}>` | Required | Data loading function |
|
|
184
|
+
| `renderItem` | `(item: any, index: number) => React.ReactNode` | Required | Item render function |
|
|
185
|
+
| `pageSize` | `number` | `50` | Items per page |
|
|
186
|
+
| `minColumnWidth` | `number` | `200` | Minimum column width |
|
|
187
|
+
| `maxColumnWidth` | `number` | - | Maximum column width |
|
|
188
|
+
| `gap` | `number` | `16` | Gap between items |
|
|
189
|
+
| `buffer` | `number` | `1500` | Buffer size (px) |
|
|
190
|
+
| `loadMoreThreshold` | `number` | `800` | Preload threshold (px) |
|
|
191
|
+
| `mapSize` | `(raw: any) => {width: number, height: number}` | - | Map data to dimensions |
|
|
192
|
+
| `enableAnimation` | `boolean` | `true` | Enable animations |
|
|
193
|
+
|
|
194
|
+
### FullWidthEqualHeightMasonry (Equal Height Layout)
|
|
195
|
+
|
|
196
|
+
| Property | Type | Default | Description |
|
|
197
|
+
|----------|------|---------|-------------|
|
|
198
|
+
| `loadData` | `(page: number, pageSize: number) => Promise<{data: any[], hasMore: boolean}>` | Required | Data loading function |
|
|
199
|
+
| `renderItem` | `(item: any, index: number) => React.ReactNode` | Required | Item render function |
|
|
200
|
+
| `pageSize` | `number` | `50` | Items per page |
|
|
201
|
+
| `targetRowHeight` | `number` | `245` | Target row height |
|
|
202
|
+
| `sizeRange` | `[number, number]` | `[230, 260]` | Row height range |
|
|
203
|
+
| `maxItemWidth` | `number` | `975` | Maximum item width |
|
|
204
|
+
| `maxStretchRatio` | `number` | `1.5` | Maximum stretch ratio |
|
|
205
|
+
| `gap` | `number` | `8` | Gap between items |
|
|
206
|
+
| `buffer` | `number` | `1500` | Buffer size (px) |
|
|
207
|
+
| `loadMoreThreshold` | `number` | `500` | Preload threshold (px) |
|
|
208
|
+
| `mapSize` | `(raw: any) => {width: number, height: number}` | - | Map data to dimensions |
|
|
209
|
+
| `enableAnimation` | `boolean` | `true` | Enable animations |
|
|
210
|
+
|
|
211
|
+
### DynamicMasonryView (Dynamic Layout)
|
|
212
|
+
|
|
213
|
+
| Property | Type | Default | Description |
|
|
214
|
+
|----------|------|---------|-------------|
|
|
215
|
+
| `isMasonry` | `boolean` | - | Controlled mode: whether to use waterfall layout |
|
|
216
|
+
| `defaultIsMasonry` | `boolean` | `true` | Uncontrolled mode: default layout type |
|
|
217
|
+
| `enableAnimation` | `boolean` | `true` | Enable animations |
|
|
218
|
+
| `loadData` | `LoadDataFn` | Required | Data loading function (can return isMasonry on first call) |
|
|
219
|
+
| `renderItem` | `(item: any, index: number, isMasonry: boolean) => React.ReactNode` | Required | Render function, isMasonry indicates current layout |
|
|
220
|
+
| `waterfallConfig` | `WaterfallConfig` | `{}` | Waterfall config (minColumnWidth, maxColumnWidth, gap, buffer) |
|
|
221
|
+
| `equalHeightConfig` | `EqualHeightConfig` | `{}` | Equal-height config (targetRowHeight, sizeRange, maxItemWidth, maxStretchRatio, gap, buffer) |
|
|
222
|
+
| `pageSize` | `number` | `50` | Items per page |
|
|
223
|
+
| `mapSize` | `(raw: any) => {width: number, height: number}` | - | Map dimensions |
|
|
224
|
+
| `renderInitialLoader` | `() => React.ReactNode` | - | Initial loading state (shown before layout type is determined) |
|
|
225
|
+
| `onLayoutTypeLoaded` | `(isMasonry: boolean) => void` | - | Layout type loaded callback |
|
|
226
|
+
| `onError` | `(error: Error) => void` | - | Error callback |
|
|
227
|
+
|
|
228
|
+
## 🎨 Custom Styling
|
|
229
|
+
|
|
230
|
+
### Custom Item Rendering
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
<VirtualMasonry
|
|
234
|
+
// ... other props
|
|
235
|
+
renderItem={(item) => (
|
|
236
|
+
<div
|
|
237
|
+
style={{
|
|
238
|
+
position: 'absolute',
|
|
239
|
+
left: item.x,
|
|
240
|
+
top: item.y,
|
|
241
|
+
width: item.width,
|
|
242
|
+
height: item.height,
|
|
243
|
+
borderRadius: '8px',
|
|
244
|
+
overflow: 'hidden',
|
|
245
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
<img
|
|
249
|
+
src={item.url}
|
|
250
|
+
alt={item.title}
|
|
251
|
+
style={{
|
|
252
|
+
width: '100%',
|
|
253
|
+
height: '100%',
|
|
254
|
+
objectFit: 'cover',
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
<div style={{
|
|
258
|
+
position: 'absolute',
|
|
259
|
+
bottom: 0,
|
|
260
|
+
left: 0,
|
|
261
|
+
right: 0,
|
|
262
|
+
padding: '12px',
|
|
263
|
+
background: 'linear-gradient(transparent, rgba(0,0,0,0.7))',
|
|
264
|
+
color: 'white',
|
|
265
|
+
}}>
|
|
266
|
+
<h3>{item.title}</h3>
|
|
267
|
+
<p>{item.description}</p>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
/>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## 🔧 Advanced Usage
|
|
275
|
+
|
|
276
|
+
### Data Mapping
|
|
277
|
+
|
|
278
|
+
If your data structure doesn't have `width` and `height` fields, use `mapSize`:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
<VirtualMasonry
|
|
282
|
+
// ... other props
|
|
283
|
+
mapSize={(item) => ({
|
|
284
|
+
width: item.imageWidth,
|
|
285
|
+
height: item.imageHeight,
|
|
286
|
+
})}
|
|
287
|
+
/>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Controlled Dynamic Layout
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
function App() {
|
|
294
|
+
const [isMasonry, setIsMasonry] = useState(true);
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<>
|
|
298
|
+
<button onClick={() => setIsMasonry(!isMasonry)}>
|
|
299
|
+
Switch Layout
|
|
300
|
+
</button>
|
|
301
|
+
<DynamicMasonryView
|
|
302
|
+
isMasonry={isMasonry}
|
|
303
|
+
// ... other props
|
|
304
|
+
/>
|
|
305
|
+
</>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## 🎯 Use Cases
|
|
311
|
+
|
|
312
|
+
- 📷 **Image Galleries** - Photo albums, image search results
|
|
313
|
+
- 🛍️ **E-commerce** - Product listings, shopping galleries
|
|
314
|
+
- 📰 **Content Feeds** - News feeds, blog posts, social media
|
|
315
|
+
- 🎨 **Portfolio Sites** - Design portfolios, artwork showcases
|
|
316
|
+
- 📱 **Media Libraries** - Video thumbnails, media collections
|
|
317
|
+
|
|
318
|
+
## 🚀 Performance
|
|
319
|
+
|
|
320
|
+
- **Virtual Scrolling**: Only renders visible items, handles 10,000+ items smoothly
|
|
321
|
+
- **Smart Preloading**: Loads next page before reaching the end
|
|
322
|
+
- **RAF Optimization**: Smooth 60fps scrolling
|
|
323
|
+
- **Responsive**: Automatically adapts to window resize
|
|
324
|
+
- **Memory Efficient**: Minimal memory footprint
|
|
325
|
+
|
|
326
|
+
## 🤝 Contributing
|
|
327
|
+
|
|
328
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
329
|
+
|
|
330
|
+
## 📄 License
|
|
331
|
+
|
|
332
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
333
|
+
|
|
334
|
+
## 🙏 Acknowledgments
|
|
335
|
+
|
|
336
|
+
Inspired by:
|
|
337
|
+
- Pinterest's waterfall layout
|
|
338
|
+
- Google Photos' justified layout
|
|
339
|
+
- React Virtualized
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
Made with ❤️ by [Your Name]
|