瀑布流布局是一种流行的布局方式,用于显示内容。它们通常用于 Web 应用程序中,以有序且视觉上吸引人的方式展示图像、视频和其他类型的内容。本文将使用 React 框架实现瀑布流布局。
什么是瀑布流布局?
瀑布流布局(Waterfall Layout),又称瀑布流式布局或砌体布局(Masonry Layouts),是一种流行的网页或移动端界面设计形式。其核心特点是多列等宽不等高的元素参差排列,视觉上像瀑布一样错落有致,随着页面滚动,新内容不断加载并附加到尾部。
瀑布流布局的优势主要体现在以下几个方面:空间利用率方面,它能紧凑排列高度不一的元素,减少空白区域,提高信息密度。用户体验方面,它能提供沉浸式浏览体验,通过无限滚动减少用户操作成本。视觉吸引力方面,错落有致的布局缓解视觉疲劳,增强探索趣味性。
实现效果
前置准备
在开始构建瀑布流布局之前,我们先使用 vite 创建一个 React 项目。如果你有现成的 React 项目,可以跳过此步骤。
npm create vite@latest
按照提示创建一个新的 React 项目。项目创建完成后,在项目的根目录安装 Magic-Grid 库。
npm install magic-grid
安装 react-infinite-scroll-hook 库来实现无限滚动。
npm install react-infinite-scroll-hook
实现瀑布流布局
创建自定义 useMagicGrid hook
为了在 React 中使用 Magic-Grid 库,我们需要创建一个 useMagicGrid hook,这里不用官方版本的 use-magic-grid 是因为官方版本的库不兼容 React 19。
// use-magic-grid.ts
import { useRef, useEffect } from "react"
import MagicGrid, { type MagicGridProps } from "magic-grid"
type useMagicGridProps = Omit<MagicGridProps, "static">
const useMagicGrid = (props: useMagicGridProps): MagicGrid => {
const gridRef = useRef<MagicGrid>(null)
const { container } = props
useEffect(() => {
if (!container) {
throw new Error("Container name or element is required")
}
if (!gridRef.current) {
gridRef.current = new MagicGrid({
...props,
items: props.items || 1,
static: false,
})
gridRef.current.listen()
return
}
const grid = gridRef.current
const currentContainer = document.querySelector(grid.containerClass)
const containerChanged = grid.container !== currentContainer
if (currentContainer && containerChanged) {
grid.setContainer(currentContainer as HTMLElement)
}
})
return gridRef.current as MagicGrid
}
export { useMagicGrid }
代码实现
在 App.tsx 文件中,添加以下代码:
// App.tsx
import { useState, useLayoutEffect } from "react"
import useInfiniteScroll from "react-infinite-scroll-hook"
import { useMagicGrid } from "./use-magic-grid"
import "./app.css"
const ARRAY_SIZE = 20
const RESPONSE_TIME_IN_MS = 1000
export interface Item {
key: number
content: string
height: number
}
interface Response {
hasNextPage: boolean
data: Item[]
}
// 获取随机数
function getRandomNumber(min: number, max: number) {
const randomIntBetween = Math.floor(Math.random() * (max - min + 1)) + min
return randomIntBetween
}
function loadItems(startCursor = 0): Promise<Response> {
return new Promise((resolve) => {
let newArray: Item[] = []
setTimeout(() => {
for (let i = startCursor; i < startCursor + ARRAY_SIZE; i++) {
// 随机生成卡片的高度
const randomIntBetween = getRandomNumber(100, 500)
const newItem = {
key: i + 1,
content: `item ${(i + 1).toString()}`,
height: randomIntBetween,
}
newArray = [...newArray, newItem]
}
resolve({ hasNextPage: true, data: newArray })
}, RESPONSE_TIME_IN_MS)
})
}
function App() {
const [loading, setLoading] = useState(false)
const [items, setItems] = useState<Item[]>([])
const [hasNextPage, setHasNextPage] = useState<boolean>(true)
const [error, setError] = useState<Error>()
const magicGrid = useMagicGrid({
container: ".items",
items: items.length,
animate: true,
useMin: true,
useTransform: false,
})
async function loadMore() {
setLoading(true)
try {
const { data, hasNextPage: newHasNextPage } = await loadItems(
items.length
)
setItems((current) => [...current, ...data])
setHasNextPage(newHasNextPage)
} catch (error_) {
setError(error_ instanceof Error ? error_ : new Error("加载错误!"))
} finally {
setLoading(false)
}
}
const [infiniteRef] = useInfiniteScroll({
loading,
hasNextPage,
onLoadMore: loadMore,
disabled: Boolean(error),
rootMargin: "0px 0px 400px 0px",
})
// 使用 useLayoutEffect 避免向下滚动时,可能出现的闪烁问题
useLayoutEffect(() => {
if (magicGrid) {
// 在容器中新增列表项时调用,重新计算布局
magicGrid.positionItems()
}
}, [items])
return (
<div className="app-container">
<div className="items">
{items.map((item) => (
<div key={item.key} className="item" style={{ height: item.height }}>
{item.content}
</div>
))}
</div>
{hasNextPage && (
<div ref={infiniteRef} className="loading">
加载中...
</div>
)}
</div>
)
}
export default App
我们还需要添加对应的css代码:
// app.css
.app-container {
padding: 20px;
}
.item {
width: 280px;
height: 500px;
background-color: antiquewhite;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
}
.loading {
display: flex;
justify-content: center;
}
总结
本文使用了 magic-grid 库在 React 中构建瀑布流布局。瀑布流布局是一种以网格状结构展示内容的绝佳方式,能够适应不同的屏幕尺寸和内容类型。通过在网页应用中实现瀑布流布局,你可以创建出具有视觉吸引力的设计来吸引用户,提升用户的使用体验。