前端开发 / 添加 React Masonry 瀑布流布局

这篇文章详细介绍了如何在 React 应用中添加瀑布流布局效果。

前端开发 / 添加 React Masonry 瀑布流布局

如果你想近一步提升用户体验,为瀑布流布局元素添加入场动画,可以参考我的这篇文章:


Masonry 瀑布流布局介绍

什么是 Masonry 瀑布流布局

Masonry 是一种网页布局技术,又被称为“瀑布流”布局。

它的主要特点是将不同高度的元素(如图片、卡片等)紧凑地排列在一起,这样做可以有效利用空间,避免页面中出现大片的空白区域。

45479304-097f6780-b73e-11e8-8821-fee771fa88ad

Masonry 实现效果

Masonry 布局非常常见,经常在图片画廊、博客或新闻网站中被使用,其中的元素可能高度不一,但又需要保持一定的顺序。

用于展示图库画廊:

EFehxjSVAAAoOrR

用于展示信息流:

9dca85e68475f703fb963855d8f02323

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

如果要实现过滤、排序功能,这是一个非常不错的库,并且拥有非常流畅的动画效果。

image-20230825213447905

官方演示 Demo : https://codepen.io/Vestride/details/ZVWmMX

更多不同应用场景 Demo : https://vestride.github.io/Shuffle/docs/demos

InfiniteGrid

支持多种不同的布局模式:

  • 固定宽度(最常见)
  • 固定高度
  • 智能布局
image-20230825220922730

官方地址: 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 库进行交互。

特点:

  1. 与 React 的生命周期方法完美集成,确保渲染顺序正确。
  2. 提供响应性和灵活性的布局,改变窗口大小时,会自动重新排版。 👍
  3. 避免直接与 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 */
  }
}

设置完成后,显示效果如下:

image-20230825180940007

开发中遇到的问题记录

首次加载时,图片堆叠显示(未使用懒加载)

问题详情:

首次加载网页时,图片会成堆叠装显示。需要所有图片加载完成之后,再自动更新为正常的瀑布流显示状态。但是这样会带来糟糕的浏览体验。

image-20230825181212031

问题原因:

问题的根源在于: Masonry 在开始布局的时候并不知道图片的确切大小,因为图片可能还没有完全加载。当图片未完全加载时,其尺寸可能是 0 或非其最终尺寸,导致 Masonry 会错误地堆叠图片。

解决方案:

有两种解决方案:

  1. 在加载图片时,传入正确的 width 和 height 参数来实现占位符。例如使用 Next.js 的 Image 组件。
  2. 在每张图片加载完成后,重新执行布局(但是这样在加载网站时,会看到一个图片布局更新的动画)。

对于第二种解决方案,详细实现方法如下:

为了确保 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();
    });
  }
};

这样,就可以正常使用瀑布流显示了。

首次加载时,图片堆叠显示(使用懒加载)

问题详情:

问题和上面类似,但是因为使用了懒加载,即使图片加载完成后,也不会自动更新布局。

image-20230825181212031

问题原因:

延迟的 JavaScript 执行 – Masonry 和其他瀑布流插件通常依赖 JavaScript 来确定每个图片应该摆放的位置。如果 JavaScript 执行被延迟或受到阻塞,图片可能会堆叠显示,直到 JS 代码执行并重新定位图片。

如果 Masonry 的布局计算在图片实际加载前完成,可能会出现布局问题。在第二次加载时,由于直接读取缓存中的图片,因此不会再有这个问题。

解决方法:

同样是以上两种解决方法:

  1. 在懒加载时,传递正确的 width 和 height 参数,实现占位符。
  2. 在每个图片加载完成后,重新布局。

同样可以使用 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 提供了多种布局效果:

image-20230828130628539

安装 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 高效地识别哪些元素发生了更改。这段代码将实现以下布局效果:
image-20230828134542312

切换布局样式

你可以使用variant参数来设置不同的布局样式,有四种预设样式:

  1. 标准( standard ):这是默认的布局样式,其中所有图像项( ImageListItems )都具有固定的行高,并且它们从上到下、从左到右地按顺序排列。
  2. 瀑布流( masonry ):在这种布局样式中,图像项( ImageListItems )可以有不同的高度,但宽度是固定的。它们按照最佳的方式从上到下、从左到右地填充,以减少任何可能的空隙。
  3. 绗缝( quilted ):在这种布局样式中,图像列表被划分为一个网格,每个网格单元可以包含 0 个、 1 个或多个图像。这种布局可以用于创建有趣的照片墙效果。
  4. 网格( woven ):这种布局样式是在行和列之间交错放置图像,使图像的布局看起来像是被编织在一起。

standard 布局效果:

image-20230826013337935

woven 布局效果:

image-20230826013245277

masonry 布局效果:

image-20230828141304241