前端开发 / 添加 React Masonry 瀑布流布局
这篇文章详细介绍了如何在 React 应用中添加瀑布流布局效果。
如果你想近一步提升用户体验,为瀑布流布局元素添加入场动画,可以参考我的这篇文章:
Masonry 瀑布流布局介绍
什么是 Masonry 瀑布流布局
Masonry 是一种网页布局技术,又被称为“瀑布流”布局。
它的主要特点是将不同高度的元素(如图片、卡片等)紧凑地排列在一起,这样做可以有效利用空间,避免页面中出现大片的空白区域。
Masonry 实现效果
Masonry 布局非常常见,经常在图片画廊、博客或新闻网站中被使用,其中的元素可能高度不一,但又需要保持一定的顺序。
用于展示图库画廊:
用于展示信息流:
Masonry 相关工具库
有很多非常优秀的 Mosonry 开箱即用的工具库:
库 | 核心特点 | 主要劣势 |
---|---|---|
masonry | – 原生 JavaScript – 支持图片加载后的布局重新计算 | – 不直接为 React 优化 – 可能需要外部依赖 |
React-masonry-component 👍 | – React 封装的 Masonry – 支持事件和多种回调 | – 较大的包大小 – 需要外部 Masonry 依赖 |
MUI Masonry/ImageList 组件👍 | – 来自 MUI 的组件库 – 多种布局效果 | – 可能需要其他 MUI 组件支持 – 较大的包大小 |
shufflejs | – 过滤和排序功能👍 – 基于 CSS3 优化性能 | – 仅限于排序和过滤,不是纯瀑布流布局库 |
bricks.js | – 轻量级 (~2kb) – 高效的分区算法 | – 功能有限,仅提供基础瀑布流功能 – 仅支持固定宽度布局 |
InfiniteGrid | – 无限滚动功能 – 支持多种布局类型 | – 复杂的配置 – 可能的性能问题 |
masonic | – 虚拟化技术 – 高性能,适用于大型图库 | – 虚拟化可能导致 SEO 问题 – 初始学习曲线 |
如果你是 React/Next.js 开发的项目,我优先推荐使用 React-masonry-component 或 MUI Masonry Component ,它们使用方法简洁搞笑,性能也非常优秀。
Masonry
Masonry 是一个流行的 JavaScript 图片布局库,由 David DeSandro 创建。 Masonry 本身并不是专门为 React 设计的,它是一个独立的 JavaScript 库,可以在任何现代的网页中使用。
如果你是使用 React 项目进行开发,建议使用 react-masonry-component — 基于 Masonry 在 React 上的一个封装。
Github 项目地址: https://github.com/desandro/masonry
一篇值得一读的介绍博客: https://tympanus.net/codrops/2013/07/02/loading-effects-for-grid-items-with-css-animations/
react-masonry-component
react-masonry-component 是 Masonry 在 React 上的一个封装,它为 React 开发者提供了一个更简单、声明式的方式来使用 Masonry ,而不需要直接与 Masonry 的 API 交互。
Github 项目地址: https://github.com/eiriklv/react-masonry-component
shufflejs
如果要实现过滤、排序功能,这是一个非常不错的库,并且拥有非常流畅的动画效果。
官方演示 Demo : https://codepen.io/Vestride/details/ZVWmMX
更多不同应用场景 Demo : https://vestride.github.io/Shuffle/docs/demos
InfiniteGrid
支持多种不同的布局模式:
- 固定宽度(最常见)
- 固定高度
- 智能布局
官方地址: https://naver.github.io/egjs-infinitegrid/
官方 Demo : https://naver.github.io/egjs-infinitegrid/storybook
react-masonry-component
react-masonry-component 库介绍
react-masonry-component 是一个专门为 React 设计的 Masonry 瀑布流布局工具。
通过使用 react-masonry-component , React 开发者可以轻松地在 React 应用程序中实现 Masonry 布局,而不需要直接与 Masonry JS 库进行交互。
特点:
- 与 React 的生命周期方法完美集成,确保渲染顺序正确。
- 提供响应性和灵活性的布局,改变窗口大小时,会自动重新排版。 👍
- 避免直接与 DOM 交互,从而保持 React 的纯净性。
Github 项目地址: https://github.com/eiriklv/react-masonry-component
安装 react-masonry-component 库
npm install react-masonry-component --legacy-peer-deps
基础使用示例
在你的 React 组件中使用 react-masonry-component :
# 以下仅用关键部分示例代码
import React from 'react';
import Masonry from 'react-masonry-component';
const masonryOptions = {
transitionDuration: 0
};
const imagesLoadedOptions = { background: '.my-bg-image-el' }
function MyComponent(props) {
return (
<Masonry
className={'my-gallery-class'} // default ''
elementType={'ul'} // default 'div'
options={masonryOptions} // default {}
disableImagesLoaded={false} // default false
updateOnEachImageLoad={false} // default false and works only if disableImagesLoaded is false
imagesLoadedOptions={imagesLoadedOptions} // default {}
>
<li>...</li>
// ...
</Masonry>
);
}
另外,我们需要在使用格外的 css 来定义显示样式,否则无法按照预期显示(将显示为遗憾一张图片):
/*react-masonry-component*/
.my-gallery-class > div {
width: calc(100% - 16px); /* Adjusting for the added margin */
margin: 8px; /* Adds a gap around each image */
}
/*响应式布局:当屏幕宽度至少为 600px 但小于 900px 时,显示两列图片。*/
@media (min-width: 600px) {
.my-gallery-class > div {
width: calc(50% - 16px); /* Adjusting for the added margin */
margin: 8px; /* Adds a gap around each image */
}
}
/*响应式布局:当屏幕宽度至少为 900px 时,显示四列图片。*/
@media (min-width: 900px) {
.my-gallery-class > div {
width: calc(25% - 16px); /* Adjusting for the added margin */
margin: 8px; /* Adds a gap around each image */
}
}
设置完成后,显示效果如下:
开发中遇到的问题记录
首次加载时,图片堆叠显示(未使用懒加载)
问题详情:
首次加载网页时,图片会成堆叠装显示。需要所有图片加载完成之后,再自动更新为正常的瀑布流显示状态。但是这样会带来糟糕的浏览体验。
问题原因:
问题的根源在于: Masonry 在开始布局的时候并不知道图片的确切大小,因为图片可能还没有完全加载。当图片未完全加载时,其尺寸可能是 0 或非其最终尺寸,导致 Masonry 会错误地堆叠图片。
解决方案:
有两种解决方案:
- 在加载图片时,传入正确的 width 和 height 参数来实现占位符。例如使用 Next.js 的 Image 组件。
- 在每张图片加载完成后,重新执行布局(但是这样在加载网站时,会看到一个图片布局更新的动画)。
对于第二种解决方案,详细实现方法如下:
为了确保 Masonry 在所有图片都已加载后执行其布局,您可以使用 imagesLoaded
这个库。它可以监听图片加载事件并告诉您何时所有图片都已经加载完成。
如果你的网站包含大量的图片(例如图库网站),等待所有图片加载完毕再进行布局是不实际的。因此,为每张图片单独使用imagesLoaded
,这样每当一个图片加载完毕,就重新布局 Masonry 。这样,您的 Masonry 布局会逐渐形成,每张图片都会在其加载完毕时被正确地放置。
下面是如何实现此策略的步骤:
安装 imagesloaded 库:
npm install imagesloaded --legacy-peer-deps
首先,为每个图片元素添加引用:
const imageRefs = useRef([]);
在映射函数中设置每个图片的引用:
{images.map((image, index) => (
<div
key={index}
ref={(el) => (imageRefs.current[index] = el)}
// ... 其他属性 ...
>
<img src={image.url} onLoad={() => handleImageLoad(index)} />
</div>
))}
创建handleImageLoad
函数来处理每张图片的加载:
const handleImageLoad = (index) => {
const masonryInstance = masonryRef.current;
const imageEl = imageRefs.current[index];
if (masonryInstance && imageEl) {
imagesLoaded(imageEl, function () {
masonryInstance.forcePack();
});
}
};
这样,就可以正常使用瀑布流显示了。
首次加载时,图片堆叠显示(使用懒加载)
问题详情:
问题和上面类似,但是因为使用了懒加载,即使图片加载完成后,也不会自动更新布局。
问题原因:
延迟的 JavaScript 执行 – Masonry 和其他瀑布流插件通常依赖 JavaScript 来确定每个图片应该摆放的位置。如果 JavaScript 执行被延迟或受到阻塞,图片可能会堆叠显示,直到 JS 代码执行并重新定位图片。
如果 Masonry 的布局计算在图片实际加载前完成,可能会出现布局问题。在第二次加载时,由于直接读取缓存中的图片,因此不会再有这个问题。
解决方法:
同样是以上两种解决方法:
- 在懒加载时,传递正确的 width 和 height 参数,实现占位符。
- 在每个图片加载完成后,重新布局。
同样可以使用 imagesLoaded
这个库来实现:每当一张图片被加载成功后,都重新触发 Masonry 布局。
# 在<DisplayImageCard>中使用图片懒加载技术,并使用 onLoad 参数来触发重新布局。
<Masonry>
{images.map((image, index) => (
<DisplayImageCard
imageSrc={image.url}
onLoad={() => handleImageLoad(index)}
/>
))}
</Masonry>
MUI ImageList/Masonry 组件
MUI 组件介绍
MUI (前称为 Material-UI )是一个流行的 React UI 库,它提供了大量的组件来帮助开发者构建响应式的 web 应用。
MUI 有两个组件可以使用瀑布流布局效果:
- ImageList :如果你是想快速实现图片文件的瀑布流布局, ImageList 组件提供最简洁的实现。
- Masonry :如果你的瀑布流布局不仅是图片文件,可以使用这个组件来实现更复杂的布局功能。
MUI 官方地址:
Masonry 是 MUI 在 v5 中引入,目前仍然属于测试版组件,需要格外安装 @mui/lab 库才能使用。如果你不需要更复杂的布局场景,推荐优先使用 ImageList 组件。
MUI 组件效果
ImageList 提供了多种布局效果:
安装 MUI 库
使用 npm 安装:
npm install @mui/material @emotion/react @emotion/styled --legacy-peer-deps
如果你要使用 Masonry 组件,则需要格外安装 @mui/lab :
npm install @mui/material @emotion/react @emotion/styled @mui/lab --legacy-peer-deps
基础使用示例
ImageList 组件保持了 MUI 简洁的风格,你可以像这样方便的调用它:
<ImageList variant="masonry" cols={3} gap={8}>
{itemData.map((item) => (
<ImageListItem key={item.img}>
<img
src={`${item.img}?w=248&fit=crop&auto=format`}
srcSet={`${item.img}?w=248&fit=crop&auto=format&dpr=2 2x`}
alt={item.title}
loading="lazy"
/>
</ImageListItem>
))}
</ImageList>
ImageList 组件:
variant="masonry"
:这定义了 ImageList 的布局类型为瀑布流( Masonry ),我们在下一部分会详细介绍。cols={3}
:这表示图片列表分为 3 列。gap={8}
:这定义了图片之间的间隙为 8 像素。
ImageListItem 组件:
- 这是每个图片的容器。
- 每个容器都有一个唯一的
key
属性,这在 React 中是必要的,因为它帮助 React 高效地识别哪些元素发生了更改。这段代码将实现以下布局效果:
切换布局样式
你可以使用variant
参数来设置不同的布局样式,有四种预设样式:
- 标准( standard ):这是默认的布局样式,其中所有图像项( ImageListItems )都具有固定的行高,并且它们从上到下、从左到右地按顺序排列。
- 瀑布流( masonry ):在这种布局样式中,图像项( ImageListItems )可以有不同的高度,但宽度是固定的。它们按照最佳的方式从上到下、从左到右地填充,以减少任何可能的空隙。
- 绗缝( quilted ):在这种布局样式中,图像列表被划分为一个网格,每个网格单元可以包含 0 个、 1 个或多个图像。这种布局可以用于创建有趣的照片墙效果。
- 网格( woven ):这种布局样式是在行和列之间交错放置图像,使图像的布局看起来像是被编织在一起。
standard 布局效果:
woven 布局效果:
masonry 布局效果: