首页
/ Material UI Lab Masonry 组件完全指南:瀑布流布局的配置、实现原理与 SSR 支持

Material UI Lab Masonry 组件完全指南:瀑布流布局的配置、实现原理与 SSR 支持

2026-09-04 18:16:38作者:羿妍玫Ivan

Masonry 是 MUI Lab 中用于构建“瀑布流”布局的组件:它把宽度一致、高度不一的内容块按行顺序排布,每个新元素都会进入当前最矮的列,从而最大化利用空间。本篇基于 MUI 官方文档与 @mui/lab 源码,完整覆盖 Masonry 的基本用法、columns / spacing / sequential / default* 等全部配置项,并深入源码剖析其基于 CSS Flexbox 负边距与 order 重排的布局引擎、ResizeObserver / MutationObserver 的响应机制,以及服务端渲染(SSR)模式的实现细节。

一、Masonry 是什么:布局规则与适用场景

官方文档 masonry.md 对 Masonry 的定义是:

Masonry lays out contents of varying dimensions as blocks of the same width and different height with configurable gaps.(Masonry 把尺寸各异的组织内容排列为宽度相同、高度不同、间隙可配置的块。)

具体布局规则有三条:

  1. 等宽变高:Masonry 维护一组宽度一致、高度不同的内容块,子元素可以是任意 React 元素,包括 <div /><img />
  2. 按行排序:内容按行(row)顺序进入布局。当某一行已被指定的列数填满后,下一个元素开始新行;
  3. 最短列优先:新元素被添加到当前最矮的列中,以此优化空间利用。

组件实现位于 Masonry.js,类型声明位于 Masonry.d.ts,测试位于 Masonry.test.js。它与另一个组件的分工值得注意:文档中明确提示,Masonry 是按行(row)排序子元素的;如果你希望图片按列(column)排序,应使用 ImageListmasonry 布局变体。

二、基本用法:最小可用示例

BasicMasonry.tsx 展示了 Masonry 的最小形态:

import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Masonry from '@mui/lab/Masonry';
import { styled } from '@mui/material/styles';

const heights = [150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80];

const Item = styled(Paper)(({ theme }) => ({
  backgroundColor: '#fff',
  ...theme.typography.body2,
  padding: theme.spacing(0.5),
  textAlign: 'center',
  color: (theme.vars || theme).palette.text.secondary,
}));

export default function BasicMasonry() {
  return (
    <Box sx={{ width: 500, minHeight: 393 }}>
      <Masonry columns={4} spacing={2}>
        {heights.map((height, index) => (
          <Item key={index} sx={{ height }}>
            {index + 1}
          </Item>
        ))}
      </Masonry>
    </Box>
  );
}

几个要点:

  • Masonry 是容器,可接收任意数量的子元素,children必填属性(见 Masonry.d.tschildren: NonNullable<React.ReactNode> 的声明,空 children 会触发 propTypes 警告,测试 Masonry.test.js 中有专门用例验证);
  • 每个子项的宽度由 Masonry 统一控制(按 columns 均分容器宽度),高度由子项内容决定;
  • 整个布局包裹在固定宽度 500px 的 Box 中,便于演示列宽分配。

三、图片瀑布流(Image Masonry)

ImageMasonry.tsx 演示了 Masonry 在图片墙场景的应用:每个子项是一个包含 Label(带编号的 Paper)和 <img />div,三列排布,图片通过 srcSet 提供 2x 高清版本并开启 loading="lazy" 懒加载:

<Masonry columns={3} spacing={2}>
  {itemData.map((item, index) => (
    <div key={index}>
      <Label>{index + 1}</Label>
      <img
        srcSet={`${item.img}?w=162&auto=format&dpr=2 2x`}
        src={`${item.img}?w=162&auto=format`}
        alt={item.title}
        loading="lazy"
        style={{
          borderBottomLeftRadius: 4,
          borderBottomRightRadius: 4,
          display: 'block',
          width: '100%',
        }}
      />
    </div>
  ))}
</Masonry>

图片场景有一个源码层面的细节:在 Masonry.jshandleResize 中,布局引擎会检查每个子项内的嵌套 <IMG> 节点,如果发现某个图片的 clientHeight === 0(图片尚未加载完成、没有实际渲染高度),整个重排会被标记为 skip,等待图片加载后再由 ResizeObserver 触发重新计算。也就是说,图片异步加载完成会自动触发一次瀑布流重排,无需手动干预。

四、变高项(Items with Variable Height)

MasonryWithVariableHeightItems.tsx 用可展开的 Accordion(最小高度各异)作为子项,说明 Masonry 对“动态高度”的支持:

<Masonry columns={3} spacing={2}>
  {heights.map((height, index) => (
    <Paper key={index}>
      <StyledAccordion sx={{ minHeight: height }}>
        <AccordionSummary expandIcon={<ExpandMoreIcon />}>
          <Typography component="span">Accordion {index + 1}</Typography>
        </AccordionSummary>
        <AccordionDetails>Contents</AccordionDetails>
      </StyledAccordion>
    </Paper>
  ))}
</Masonry>

文档特别强调:为了满足“新元素永远进入最短列”的规则,项目之间可能在不同列之间移动。从源码结构看,这正是由 Masonry.test.js 中 “should re-compute the height of masonry when dimensions of any child change” 用例所验证的行为:当某个子项的高度从 20px 变为 10px 后,容器总高度会随之重算——因为任何子项尺寸变化都会通过 ResizeObserver 触发一次完整重排(见后文第五节原理分析)。

五、配置列数:columns

固定列数

FixedColumns.tsx 演示 columns={4}:容器宽度被等分为 4 份,每份再减去 spacing 得到子项实际宽度。

响应式列数

columns 接受 MUI 标准的响应式值。ResponsiveColumns.tsx 中:

<Masonry columns={{ xs: 3, sm: 4 }} spacing={2}>

即小屏 3 列、sm 断点及以上 4 列。从类型声明看(Masonry.d.ts),columns 的类型是 ResponsiveStyleValue<number | string>,因此除了对象形式 { xs, sm, md, lg, xl },还支持数组形式 [3, 'sm', 4]

源码印证:在 Masonry.jsgetStyle 函数中,columns 先经 unstable_resolveBreakpointValues 解析为各断点取值,再由 handleBreakpoints 为每个断点生成媒体查询样式,核心公式是子项宽度:

width = (100 / columnValue).toFixed(2) + '%';
// 子项实际宽度:calc(<width>% - <spacing>)

测试文件 Masonry.test.js 的 “should generate correct responsive styles regardless of breakpoints order” 用例还验证了一个实用细节:响应式对象的键序不影响结果,传入 { sm: 5, md: 7, xs: 3 } 与按断点升序排列生成完全相同的媒体查询样式。

六、配置间距:spacing

固定间距

FixedSpacing.tsx 使用 spacing={3}。这里有一个关键约定(文档原文):

传入 spacing 的值会被乘以主题(theme)的 spacing 字段。

spacing={3} 在默认主题下(spacing: (factor) => 8 * factor px)等效于 24px 的间隙。这一行为在源码中由 createUnarySpacing(theme) 生成的 transformer 实现:对数字值(以及可解析为数字的字符串)执行 getValue(transformer, number),其余字符串(如 '4px')则原样使用。

响应式间距

ResponsiveSpacing.tsx 中:

<Masonry columns={3} spacing={{ xs: 1, sm: 2, md: 3 }}>

同样受 ResponsiveStyleValue 约束,可传对象或数组。

间距的实现机制值得单独说明。Masonry 并不使用 CSS gap,而是采用“容器负边距 + 子项四向半边距”的经典做法,getStyle 中生成:

{
  margin: `calc(0px - (${spacing} / 2))`,   // 容器:抵消外溢的边距
  '& > *': { margin: `calc(${spacing} / 2)` } // 每个子项:四周 spacing/2
}

这样无论水平还是垂直方向,任意两个相邻子项之间的净距离都恰好是 spacing。同时容器高度会在布局引擎测得 maxColumnHeight(最矮列之外最高的那一列总高)后设置为 maxColumnHeight + spacing,测试用例 “should apply correct default styles” 精确断言了这三个值(容器负边距 -spacing/2、子项边距 spacing/2、子项宽度 width/columns - spacing)。

七、顺序模式:sequential

Sequential.tsx 演示:

<Masonry
  columns={4}
  spacing={2}
  defaultHeight={450}
  defaultColumns={4}
  defaultSpacing={1}
  sequential
>

开启 sequential 后,元素按从左到右的顺序依次填入各列(第 1、2、3、4 个元素分别在第 1~4 列,第 5 个元素回到第 1 列),而不是进入当前最短列。适合需要保持阅读顺序严格的场景。

源码印证Masonry.jshandleResize 中两种策略是并列分支:

if (sequential) {
  columnHeights[nextOrder - 1] += childHeight;
  child.style.order = nextOrder;
  nextOrder += 1;
  if (nextOrder > currentNumberOfColumns) {
    nextOrder = 1;
  }
} else {
  const currentMinColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
  columnHeights[currentMinColumnIndex] += childHeight;
  child.style.order = currentMinColumnIndex + 1;
}

测试用例 “should place children in sequential order”(浏览器环境)验证了 2 列 3 个子项时计算样式 order 依次为 1, 2, 1

八、服务端渲染(SSR):defaultHeight / defaultColumns / defaultSpacing

Masonry 的动态布局依赖浏览器 API(读取子项实际高度),在服务端执行时这些值不可用。为此组件提供三个仅用于 SSR 的 prop(见 SSRMasonry.tsx):

<Masonry
  columns={4}
  spacing={2}
  defaultHeight={450}
  defaultColumns={4}
  defaultSpacing={1}
>

文档中的注意事项原文:

defaultHeight 应当足够大以容纳所有行。另外需要注意,在服务端渲染的情况下,元素不会被添加到最短列。

进入 SSR 分支的条件Masonry.js L189-L196):

const isSSR =
  !maxColumnHeight &&
  defaultHeight &&
  defaultColumns !== undefined &&
  defaultSpacing !== undefined;

即首次服务端渲染(尚未测得 maxColumnHeight)且三个 default* prop 全部提供时启用。SSR 分支生成的纯 CSS 样式为(L52-L77):

  • 容器高度固定为 defaultHeight(px);
  • 容器与子项使用 defaultSpacing 换算出的固定负/正半边距;
  • 每个子项宽度为 calc(100/defaultColumns% - defaultSpacing)
  • nth-of-type(defaultColumns n + i) 选择器为子项依次赋予 order: 1..defaultColumns,实现“按行左到右”的确定性列分配——这正是文档所说“SSR 下不进入最短列”的原因:纯 CSS 无法知道每项高度,只能做静态轮转。

浏览器端水合(hydrate)完成后,handleResize 会立即运行,用真实测量值接管布局,SSR 静态样式随之被动态 order 与容器高度覆盖。测试用例 “should support server-side rendering” 对 isSSR: true 时的完整样式对象(含 nth-of-type(4n+1)order: 1 等规则)做了精确断言。

九、布局引擎原理:从源码看 Masonry 如何工作

结合 Masonry.js 全文,Masonry 的运行时机制可以归纳为四部分:

1. 静态骨架:Flexbox 容器

getStyle 生成的基础容器样式是:

{
  width: '100%',
  display: 'flex',
  flexFlow: 'column wrap',      // 纵向主轴 + 允许换行
  alignContent: 'flex-start',
  boxSizing: 'border-box',
}

注意主轴方向是 column:每个“行”是 Flex 容器的一条主轴,换行产生下一行。子项通过 order 属性决定落入哪一列,从而把二维瀑布流映射为“纵向 Flex + order 重排”的一维问题。

2. 防合并哨兵:line breaks

源码 L353-L358 揭示了一个精妙设计——组件末尾渲染一组透明哨兵元素:

// A line break is added to the end of each column to prevent columns from merging.
const lineBreaks = new Array(numberOfLineBreaks).fill('').map((_, index) => (
  <span key={index} data-class="line-break"
    style={{ flexBasis: '100%', width: 0, margin: 0, padding: 0, order: index + 1 }} />
));

每个哨兵 flexBasis: '100%',按 order: 1..N 占据各列末尾。它的作用是强制每列独占一条主轴轨道,防止相邻列在无内容的情况下发生视觉合并;哨兵自身宽高均为 0,不占据可见空间。numberOfLineBreaks 初始为 0(或 SSR 时为 defaultColumns - 1),在首次重排后更新为“实际列数 - 1”。

3. 动态重排:ResizeObserver + MutationObserver

useEnhancedEffect(L298-L349)建立了完整的响应管线:

  • 对容器内每个子项 resizeObserver.observe(childNode),子项尺寸变化即触发重排;
  • MutationObserver 监听 childList,动态新增的子节点自动纳入观察、移除的自动解绑,并在每次变更回调中直接调用 handleResize()——因此列表项的增删不需要任何手动刷新
  • 重排入口做了 16ms(约 60fps)的防抖;
  • 若运行环境缺少 ResizeObserverMutationObserver(如纯 Node 环境),直接退出不观测——这解释了为什么 SSR 场景必须显式提供 default* prop。

4. 高度测量与列分配:handleResize

handleResize(L211-L296)的核心步骤:

  1. 取容器 clientWidth 与第一个可见子项的宽度及左右 margin,反推出实际列数Math.round(parentWidth / (firstChildWidth + marginLeft + marginRight))——因此响应式列数变化(断点切换)无需额外监听,宽度一变自然重算;
  2. 遍历子项,读取计算样式得到每项高度(含上下 margin);高度为 0 或内含未加载图片(IMG.clientHeight === 0)的子项触发 skip,本次重排放弃,等待下一次观察事件;
  3. sequential 或最短列策略为每个子项写入 child.style.order
  4. 通过 ReactDOM.flushSync 同步更新两个 state:maxColumnHeight(驱动容器高度)与 numberOfLineBreaks(驱动哨兵数量)。flushSync 保证高度与 order 在同一次同步提交中生效,避免布局抖动。

十、Props 速查表

综合 Masonry.d.ts 与实现代码中的默认值(L174-L185):

Prop 类型 默认值 说明
children NonNullable<ReactNode> 必填 内容,可包含任意元素
columns ResponsiveStyleValue<number | string> 4 列数,支持对象/数组响应式值
spacing ResponsiveStyleValue<number | string> 1 子项间距,为主题 spacing 的倍数(数字时)
sequential boolean false true 时按左到右顺序填入而非最短列
defaultColumns number 仅 SSR:静态列数
defaultHeight number(px) 仅 SSR:容器固定高度,应足够容纳所有行
defaultSpacing number 仅 SSR:spacing 同义,主题倍数
component ElementType 'div' 根节点使用的 HTML 元素或组件
classes Partial<MasonryClasses> 类名覆盖,目前仅 root 插槽(见 masonryClasses.ts
sx SxProps<Theme> 追加样式

组件注册名为 MuiMasonry(styled 声明 L163-L166),因此也支持通过主题的 components: { MuiMasonry: { ... } } 做全局样式定制;useThemeProps 的接入(L169-L172)保证了这一能力。

十一、实践建议与边界

基于文档与源码行为,总结如下适用前提与限制:

  1. 布局计算发生在浏览器端。没有 ResizeObserver/MutationObserver 的环境不会触发任何列分配,SSR 场景务必成对提供 defaultColumns / defaultHeight / defaultSpacing
  2. defaultHeight 偏大会留白、偏小会裁切:文档建议“足够大以渲染所有行”,且 SSR 阶段列分配是静态轮转(nth-of-type 规则,测试中有精确断言),水合后才会切换为最短列策略,首屏可能出现短暂的列位调整;
  3. 未加载的图片不参与本轮重排:Masonry 会跳过 clientHeight === 0 的图片子项等待其加载,图片墙场景建议配合 loading="lazy" 并预留稳定尺寸,减少重排次数;
  4. spacing 是倍数而非像素:传入数字时经主题 spacing 函数换算,若需像素级控制可传入可解析字符串或 '0px' 这类字面值;
  5. 与 ImageList 的选型:按行排序、元素需动态重排选 Masonry;按列排序的图片墙用 ImageList 的 masonry 变体(官方文档在 masonry.md 中给出了该指引)。

参考文件

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341