Ant Design Masonry 瀑布流组件完全指南:从响应式配置到源码级布局算法
2026/9/8 23:07:55 网站建设 项目流程

Ant Design Masonry 瀑布流组件完全指南:从响应式配置到源码级布局算法

【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design

本文是 Ant Design(当前仓库)中Masonry瀑布流组件的技术指南。Masonry 是一个"等宽但不等高、按列数均匀排布"的瀑布流布局组件,用于高效展示图片墙、卡片流等不规则高度内容。读完本文,你将掌握 Masonry 的全部 API(columnsgutteritemsitemRenderfreshonLayoutChange等)、响应式列数与间距配置方法、图片/动态内容场景下的尺寸监听机制,以及其背后"最矮列优先"的经典瀑布流布局算法在源码中的实现细节。

一、组件定位与使用时机

Masonry 属于布局类组件,其组件文档(components/masonry/index.zh-CN.md)给出的定位是:瀑布流布局组件,用于展示不同高度的内容。与传统的多行栅格不同,瀑布流要求每一列都能"紧凑地"叠放高低不一的条目,行尾不再对齐,从而最大化利用纵向空间。

从文档看,它最适合以下三类场景:

  • 展示不规则高度的图片或卡片时(典型如照片墙、瀑布流信息流);
  • 需要按照列数均匀分布内容时;
  • 需要响应式调整列数时(窄屏一列、宽屏多列)。

值得一提的前提:组件文档的元信息tag: 6.0.0表明该组件及其语义化样式能力自该版本起提供,使用前请确认你所依赖的 antd 版本包含 Masonry(可在当前仓库 components/index.ts 中确认其导出关系)。

二、快速上手:一个最简瀑布流

Masonry 的核心用法是把"高度数据"通过items传入,并用itemRender决定每个条目如何渲染。以下节选自官方示例 components/masonry/demo/basic.tsx:

import React from 'react'; import { Card, Masonry } from 'antd'; import type { MasonryProps } from 'antd'; type MasonryItemType = NonNullable<MasonryProps<number>['items']>[number]; // 每个条目的高度数据(实际场景中通常来自接口) const heights = [150, 50, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 60, 50, 80]; const App: React.FC = () => ( <Masonry columns={4} gutter={16} items={heights.map((height, index) => ({ key: `item-${index}`, data: height }))} itemRender={({ data, index }) => ( <Card size="small" style={{ height: data }}> {index + 1} </Card> )} /> ); export default App;

把这份代码跑起来,你就能看到 15 张高度各异的小卡片被自动分配到 4 列中:新增的卡片总会被塞进"当前最短的那一列"。示例还演示了一种常见诉求——某个条目需要完全自定义内容:此时直接给对应 item 设置children,它的渲染优先级高于itemRender(源码见 MasonryItem.tsx 中item.children ?? itemRender?.(...)的判断逻辑)。这样你就既可以用统一itemRender批量生成条目,又可以为特殊 item 单独定制 DOM。

三、完整 API 参考

Masonry 复用 antd 的通用属性(classNamestyleprefixClsrootClassName等),并定义了自己专属的 props 与子组件。下面的参数表与说明以官方中文文档为准,并结合源码对每个参数的实际效果做了补充。

Masonry 主组件

参数说明类型默认值版本全局配置
classNames自定义组件内部各语义化结构的 class,支持对象或函数Record<SemanticDOM, string> \| ((info: { props }) => Record<SemanticDOM, string>)-6.0.06.0.0
columns列数,可以是固定值或响应式配置number \| { xs?; sm?; md? }3-×
fresh是否持续监听子项尺寸变化booleanfalse-×
gutter间距,固定值、响应式配置或水平/垂直二元组Gap \| [Gap, Gap]0-×
items瀑布流项MasonryItem[]--×
itemRender自定义项渲染(item: MasonryItem) => React.ReactNode--×
styles语义化结构 style,支持对象和函数Record<SemanticDOM, CSSProperties> \| ((info: { props }) => Record<SemanticDOM, CSSProperties>)-6.0.06.0.0
onLayoutChange列排序回调({ key: React.Key; column: number }[]) => void--×

表中"全局配置"列打(即 classNames / styles)的项,可通过 ConfigProvider 的componentConfig针对全局统一注入,Masonry 内部通过useComponentConfig('masonry')读取(见 Masonry.tsx);打×的项(如列数、间距、数据)仅支持组件级配置。

MasonryItem(items 的元素)

items中的每一项在类型上就是MasonryItem(MasonryItem.tsx),完整字段如下:

参数说明类型默认值
children自定义展示内容,相对itemRender具有更高优先级React.ReactNode-
column自定义所在列number-
data自定义存储数据(如高度、图片地址),透传给itemRenderT-
height高度number-
key唯一标识string \| number-

需要特别说明的是data:它不是渲染内容,而是"数据载荷",最终会出现在itemRender回调的参数里。例如图片场景中常把图片 URL 存进data,渲染时再取出,见下文的图片示例。

Gap 间距类型

官方文档给出gutter的取值类型为:

type Gap = undefined | number | Partial<Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', number>>;

也就是说:

  • gutter={16}:所有间隙固定 16px;
  • gutter={{ xs: 8, sm: 12, md: 16 }}:随屏幕断点变化;
  • gutter={[16, 24]}:水平间隙 16px、垂直间隙 24px(水平/垂直不对称的二元组写法)。

Gap 与 columns 的区别值得留意:columns 目前只按列维度把内容均匀铺开,而 gutter 复用的是 GridRow的 gutter 语义——从源码看gutter?: RowProps['gutter'](Masonry.tsx),并通过 grid 的useGutter在对应断点解析出[horizontalGutter, verticalGutter],其中垂直间距同时参与列高的累加计算(见下文原理章节)。

四、响应式:按断点切换列数与间距

真实业务里,瀑布流必须适配手机、平板和桌面。Masonry 把列数、间距都做成了响应式配置,见官方示例 components/masonry/demo/responsive.tsx:

import React from 'react'; import { Card, Masonry } from 'antd'; const heights = [120, 55, 85, 160, 95, 140, 75, 110, 65, 130, 90, 145, 55, 100, 80]; const App: React.FC = () => { const items = heights.map((height, index) => ({ key: `item-${index}`, data: height, })); return ( <Masonry columns={{ xs: 1, sm: 2, md: 3, lg: 4 }} gutter={{ xs: 8, sm: 12, md: 16 }} items={items} itemRender={(item) => ( <Card size="small" style={{ height: item.data }}> {item.index + 1} </Card> )} /> ); };

官方 API 表格把columns的响应式类型写为{ xs?; sm?; md? },但请注意响应式示例本身就用到了lg: 4。结合源码确认:columns?: number | Partial<Record<Breakpoint, number>>(Masonry.tsx),而Breakpoint枚举自responsiveObserverresponsiveArray(components/_util/responsiveObserver.ts),即xxxl / xxl / xl / lg / md / sm / xs全量断点都是可用的。

列数的解析逻辑也很讲究(Masonry.tsx):

  1. 未传columns时默认返回3
  2. 传数字时直接返回该数字;
  3. 传对象时,按responsiveArray的顺序(从大到小)找到第一个当前屏幕命中且已配置列数的断点;一个都没命中则回退columns.xs ?? 1

因此{ xs: 1, sm: 2, md: 3, lg: 4 }在宽屏取 4、平板取 3、窄屏依次降为 2、1,整个切换由useBreakpoint驱动,无需手动写 media query。

五、图片墙场景:自动感知图片加载完成后的尺寸

瀑布流最常见的内容就是图片——而图片恰恰有一个天然难点:加载完成前浏览器不知道它的真实高度。Masonry 对此做了两层处理,源码集中在 Masonry.tsx:

  • 容器最外层包裹ResizeObserver,容器尺寸变化(例如窗口缩放)会触发重排;
  • 容器上直接绑定了onLoadonError,借助 React 的事件冒泡,任何一张图片加载完成或加载失败都会触发一次"重新测量尺寸"。

于是图片墙可以写得非常干净。官方图片示例 components/masonry/demo/image.tsx 的渲染逻辑只有两件事——把 URL 放进data,渲染时铺满宽度并等待高度被测量:

import React from 'react'; import { Masonry } from 'antd'; const imageList = [ 'https://images.unsplash.com/photo-1510001618818-4b4e3d86bf0f', // ... 若干图片 URL ]; const App = () => ( <Masonry columns={4} gutter={16} items={imageList.map((img, index) => ({ key: `item-${index}`, data: img, }))} itemRender={({ data }) => ( <img src={`${data}?w=523&auto=format`} alt="sample" style={{ width: '100%' }} /> )} /> );

测量是异步合并的collectItemSize通过useDelay包装(components/masonry/hooks/useDelay.ts),内部基于@rc-component/utilraf做 requestAnimationFrame 节流——同一帧内多次触发的尺寸变化只会合并成一次重排,避免图片逐张加载时频繁计算导致的抖动与性能浪费。测量时组件会遍历所有已挂载的 item,读取其getBoundingClientRect().height(每个 item 的真实 DOM 引用由 useRefs.ts 维护的Map<key, element>提供),只在高度集合真正变化时才触发位置重算。

六、动态增删条目:配合 onLayoutChange 保持位置

瀑布流信息流往往需要"上拉加载更多""手动删除卡片"之类的动态操作。Masonry 的默认策略是把条目稳定地按序放进各列,这本身就能避免频繁增删引起的整片跳动;当需要更精细地控制"每个条目落在第几列"时,可以借助onLayoutChange把计算结果"写回"items。

官方动态示例 components/masonry/demo/dynamic.tsx 的做法是:

  1. 初始化时为每条数据显式指定column: index % 4,让内容均匀分到 4 列;
  2. 通过onLayoutChange回调拿到最新的{ key, column }映射(回调签名即({ key: React.Key; column: number }[]) => void),并把新的列号写回 state;
  3. 添加新卡片时不再指定column(由算法自动寻找最矮列),删除卡片时按key过滤即可。
<Masonry columns={4} gutter={16} items={items} onLayoutChange={(sortedItems) => { setItems((prevItems) => prevItems.map((item) => { const matchItem = sortedItems.find((sortedItem) => sortedItem.key === item.key); return matchItem ? { ...item, column: matchItem.column } : item; }), ); }} ... />

从源码看,onLayoutChange的触发有两条链路(Masonry.tsx):先由useLayoutEffect在"所有条目都拿到 position"时把最新的[item, column]列表缓存下来,再在 items 数量与缓存一致时向外抛出。这一机制保证了回调中的顺序、数量与当前渲染保持一致,开发者可以放心用它做受控列号的同步。其位置计算的另一条铁律见 usePositions.ts 的注释:"Always get stable positions by order instead of dynamic adjust for next item height"——即永远按 items 的原始顺序为条目分配位置,绝不会因为后一个条目的高度去调整前一个条目的位置,这从根上保证了增删 item 时的视觉稳定。

七、fresh 模式:持续监听子项内部尺寸变化

需要澄清一个易混点:常规模式下,组件的尺寸监听并不是"每时每刻"都开着的。默认fresh={false}时,组件只在 items 变化、列数变化、容器 ResizeObserver 触发以及图片 load/error 等关键时机重新测量。这对绝大多数静态内容(卡片、普通图片)已经足够且性能更优。

但如果卡片内部包含会在渲染之后才改变尺寸的内容(例如懒加载的富文本、折叠展开区、动态插入的媒体),就需要开启:

参数说明类型默认值
fresh是否持续监听子项尺寸变化booleanfalse

fresh的底层开关非常直观(Masonry.tsx):开启后每个 item 会被额外的ResizeObserver包裹,其onResize持续驱动collectItemSize;关闭时该 observer 不挂载(对应 MasonryItem.tsx 中"仅在onResize存在时才包裹 ResizeObserver"的惰性逻辑)。由于 demo 列表中以 debug 方式标注了"持续更新"示例(components/masonry/demo/fresh.tsx),建议先在本地验证你的内容场景是否真的需要 fresh,再决定是否开启,避免不必要的持续监听开销。

八、语义化结构与样式定制:classNames / styles / Semantic DOM

Masonry 的样式定制遵循 antd 的"语义化 DOM + classNames/styles"体系。组件结构为两层(定义见 Masonry.tsx,交互式示意见 components/masonry/demo/_semantic.tsx):

语义节点对应说明
root根元素,设置相对定位、flex 布局与瀑布流容器样式
item条目元素,设置绝对定位、宽度计算、过渡动画与瀑布流项目样式

对应到 API 上,classNames/styles均支持对象形式,也支持接收{ props }的函数形式(函数可在拿到最终合并后的 props 后再决定样式),并且都能通过 ConfigProvider 全局注入。官方"自定义语义结构的样式和类"示例见 components/masonry/demo/style-class.tsx,一种典型写法是:

<Masonry classNames={{ root: 'my-masonry-root', item: 'my-masonry-item', }} styles={{ root: { background: token.colorFillTertiary }, item: { borderRadius: 8 }, }} ... />

当这样编写时,实际渲染结构大致为:div.root容器内,每个MasonryItem渲染为带prefixCls-item类名且绝对定位的div.item,其中prefixCls默认解析为ant-masonry(前缀由getPrefixCls('masonry', ...)产生,Masonry.tsx)。

九、布局原理:源码级的"最矮列优先"算法

理解了用法之后,值得看看 Masonry 究竟如何实现"等高列内的瀑布流"。整个布局核心集中在 usePositions.ts,算法可以用 5 步概括:

  1. 初始化一个长度为columnCount、元素全为 0 的"列高数组"columnHeights
  2. 按 items 顺序遍历测量结果[itemKey, itemHeight, itemColumn?]
  3. 若条目未显式指定column,则选中"当前最矮的列"(columnHeights.indexOf(Math.min(...columnHeights)));若指定了,则直接使用该列号,并做Math.min(column, columnCount - 1)越界钳制;
  4. 把该条目的top记为columnHeights[targetColumn],随后累加columnHeights[targetColumn] += itemHeight + verticalGutter
  5. 最终容器总高度取max(columnHeights) - verticalGutter(减掉最后一列多算的底部间距)。

拿到每个 item 的{ column, top }后,Masonry 在渲染层用 CSS 变量把位置落到 DOM 上(Masonry.tsx):

itemStyle = { [varName('item-width')]: `calc((100% + ${horizontalGutter}px) / ${columnCount})`, insetInlineStart: `calc(${varRef('item-width')} * ${columnIndex})`, width: `calc(${varRef('item-width')} - ${horizontalGutter}px)`, top: position.top, position: 'absolute', }

即:先按"容器宽度 + 水平间距"均分得到每条目宽度,再通过insetInlineStart偏移到对应列。inset-inline语义也让组件天然兼容 RTL——样式文件在 RTL 时会追加direction: rtl的修饰类(components/masonry/style/index.ts)。

条目在列间移动或增删时的动画由两部分构成:增删使用CSSMotionList的 fade 出入场;重排时的位移过渡则由样式表对left/right/top应用motionDurationSlow的过渡实现(style/index.ts)。当列数变化(例如窗口缩放导致 4 列变 2 列)时,条目会平滑滑入新位置而不是突兀跳动。

十、Design Token 与全局配置现状

最后说明样式体系的两个现状,避免使用时的误区:

  • Design Token:Masonry 文档页中 Design Token 区(<ComponentTokenTable component="Masonry" />)目前为空,对应源码中ComponentToken也是一个空接口(components/masonry/style/index.ts),即该组件目前不提供专属 token,视觉变量沿用全局主题令牌(如motionDurationSlowmotionEaseOut等)。
  • ConfigProvider 全局配置:如上文 API 表所示,只有classNames/styles两类"语义化样式"支持通过componentConfig全局统一配置(在 Masonry 内读取键为masonry);columnsgutterfreshitemsitemRenderonLayoutChange均只支持组件级配置。

十一、进一步阅读与测试验证

如果你想深入理解 Masonry 的行为边界,可以继续阅读当前仓库中的以下文件:

  • 组件实现:components/masonry/Masonry.tsx、components/masonry/MasonryItem.tsx
  • 布局与测量 hooks:components/masonry/hooks/usePositions.ts、components/masonry/hooks/useDelay.ts、components/masonry/hooks/useRefs.ts
  • 样式实现:components/masonry/style/index.ts
  • 官方示例(可直接复制运行):basic、responsive、image、dynamic、style-class、fresh
  • 测试用例(含快照):components/masonry/tests/index.test.tsx、components/masonry/tests/semantic.test.tsx,其中语义化测试会逐项断言root/item节点是否携带预期的 class。

组件英文文档见 components/masonry/index.en-US.md,两份文档的结构与参数保持一致。总体而言,Masonry 把"测量—分配—定位—动画"这条瀑布流核心链路封装成了声明式 API,你只需提供数据与渲染函数,列高计算、响应式切换、图片重测和位移过渡都由组件内部完成;在需要极致控制或排查布局抖动时,则可依据本文第九节的算法路径,从usePositions与尺寸收集代码入手定位问题。

【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询