首页
/ Material UI Image List 完全指南:从标准网格到 Masonry 布局与 ImageListItemBar 实战

Material UI Image List 完全指南:从标准网格到 Masonry 布局与 ImageListItemBar 实战

2026-09-04 22:21:50作者:何将鹤

本文围绕 Material UI(@mui/material)的 Image List 组件体系展开,覆盖官方文档中全部八种用法——standard、quilted、woven、masonry 四种变体,以及 ImageListItemBar 标题栏的各种位置定制。在给出可直接运行的完整示例的同时,结合仓库源码解析 CSS Grid 与 CSS 多列布局两种底层实现路径、默认值与插槽结构,帮助你彻底掌握图片画廊类页面(相册、商品墙、作品集)的搭建方案。

组件体系与源码位置

Image List 功能由三个组件协作完成,源码位于 packages/mui-material 包下:

组件 作用 源码位置
ImageList 列表容器,负责列数、间距、布局变体 ImageList.js
ImageListItem 单个列表项,决定跨越的行列数 ImageListItem.js
ImageListItemBar 叠在图片上的标题栏,支持标题、副标题与操作图标 ImageListItemBar.js

ImageList 的核心 props 及默认值(由 ImageList.js 中的解构与 PropTypes 注释确认):

Prop 类型 默认值 说明
cols number 2 列数
rowHeight number | 'auto' 'auto' 单行高度(px),标准/拼布变体中决定行高基准
gap number 4 项与项之间的间距(px)
variant 'standard' | 'quilted' | 'woven' | 'masonry' 'standard' 布局变体
component element 'ul' 根节点,默认为无序列表

官方文档对应页面位于 image-list.md,所有可运行示例位于 docs/data/material/components/image-list 目录下。

标准图片列表(standard)

标准图片列表适合重要性相同的条目:容器尺寸、宽高比和间距完全一致。这是默认变体,只需设置 colsrowHeight

import ImageList from '@mui/material/ImageList';
import ImageListItem from '@mui/material/ImageListItem';

export default function StandardImageList() {
  return (
    <ImageList sx={{ width: 500, height: 450 }} cols={3} rowHeight={164}>
      {itemData.map((item) => (
        <ImageListItem key={item.img}>
          <img
            srcSet={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2 2x`}
            src={`${item.img}?w=164&h=164&fit=crop&auto=format`}
            alt={item.title}
            loading="lazy"
          />
        </ImageListItem>
      ))}
    </ImageList>
  );
}

要点:

  • rowHeight={164} 定义了单行基准高度,cols={3} 时每格图片按 164×164 裁剪,因此 src 中请求的 w=164&h=164 与网格单元一一对应;
  • srcSet 提供 dpr=2 的 2 倍图,保证高分屏下清晰;
  • loading="lazy" 延迟加载,避免一次性请求全部图片。

完整示例可参考 StandardImageList.tsx

拼布图片列表(quilted)

拼布列表通过让部分条目跨越多行多列来强调某些内容,形成视觉层级。启用方式是指定 variant="quilted",并给 ImageListItem 传入 cols / rows

import ImageList from '@mui/material/ImageList';
import ImageListItem from '@mui/material/ImageListItem';

function srcset(image: string, size: number, rows = 1, cols = 1) {
  return {
    src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`,
    srcSet: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format&dpr=2 2x`,
  };
}

export default function QuiltedImageList() {
  return (
    <ImageList sx={{ width: 500, height: 450 }} variant="quilted" cols={4} rowHeight={121}>
      {itemData.map((item) => (
        <ImageListItem key={item.img} cols={item.cols || 1} rows={item.rows || 1}>
          <img
            {...srcset(item.img, 121, item.rows, item.cols)}
            alt={item.title}
            loading="lazy"
          />
        </ImageListItem>
      ))}
    </ImageList>
  );
}

// 数据示例:Breakfast 占 2 行 2 列,Burger 占 1 行 1 列……
const itemData = [
  { img: '…/photo-1551963831-b3b1ca40c98e', title: 'Breakfast', rows: 2, cols: 2 },
  { img: '…/photo-1551782450-a2132b4ba21d', title: 'Burger' },
  { img: '…/photo-1522770179533-24471fcdba45', title: 'Camera' },
  { img: '…/photo-1444418776041-9c7e33cc5a9c', title: 'Coffee', cols: 2 },
  // ……其余 8 项
];

(完整 12 项数据见 QuiltedImageList.tsx

从源码看,ImageListItem.js 中通过 gridColumnEnd: span ${cols}gridRowEnd: span ${rows} 实现跨格,这正是 CSS Grid 的跨行跨列语法。而 srcset 辅助函数按 size * colssize * rows 计算请求尺寸——跨越 2×2 的图片会请求 242×242 的裁剪图,保证大图不模糊。这是一个值得借鉴的技巧:图片请求尺寸必须等于实际渲染尺寸

编织图片列表(woven)

编织列表使用交替的容器宽高比制造节奏感,适合浏览地位平等的内容。与标准变体相比,唯一区别是 variant="woven"gap

import ImageList from '@mui/material/ImageList';
import ImageListItem from '@mui/material/ImageListItem';

export default function WovenImageList() {
  return (
    <ImageList sx={{ width: 500, height: 450 }} variant="woven" cols={3} gap={8}>
      {itemData.map((item) => (
        <ImageListItem key={item.img}>
          <img
            srcSet={`${item.img}?w=161&fit=crop&auto=format&dpr=2 2x`}
            src={`${item.img}?w=161&fit=crop&auto=format`}
            alt={item.title}
            loading="lazy"
          />
        </ImageListItem>
      ))}
    </ImageList>
  );
}

(完整示例见 WovenImageList.tsx

woven 变体下奇数列的图片高度为 1 行、偶数列为 2 行,形成交错节奏;图片只需指定宽度(w=161),高度由网格行高自动裁剪。

瀑布流图片列表(masonry)

Masonry 变体使用动态的容器高度来反映每张图片的真实宽高比,适合浏览未经裁剪的原始内容:

import Box from '@mui/material/Box';
import ImageList from '@mui/material/ImageList';
import ImageListItem from '@mui/material/ImageListItem';

export default function MasonryImageList() {
  return (
    <Box sx={{ width: 500, height: 450, overflowY: 'scroll' }}>
      <ImageList variant="masonry" cols={3} gap={8}>
        {itemData.map((item) => (
          <ImageListItem key={item.img}>
            <img
              srcSet={`${item.img}?w=248&fit=crop&auto=format&dpr=2 2x`}
              src={`${item.img}?w=248&fit=crop&auto=format`}
              alt={item.title}
              loading="lazy"
            />
          </ImageListItem>
        ))}
      </ImageList>
    </Box>
  );
}

(完整示例见 MasonryImageList.tsx

ImageList.js 源码可以看到 masonry 与其他变体的本质区别:

  • standard / quilted / woven:根节点是 CSS Grid,内联样式为 gridTemplateColumns: repeat(${cols}, 1fr) 加上 gap
  • masonry:切换为 columnCount: cols, columnGap: gap 的 CSS 多列布局,且根节点样式强制 display: block

这意味着 masonry 下 rowHeight 不起作用(列高由图片自然高度决定),同时项目按列方向排布、DOM 顺序自上而下流入各列,因此外层用 Box 包裹并设置 overflowY: 'scroll' 提供滚动容器。

带标题栏的列表(ImageListItemBar)

ImageListItemBar 为每个条目叠加一层信息栏,可容纳 titlesubtitle 和一个次要操作 IconButton

import ImageList from '@mui/material/ImageList';
import ImageListItem from '@mui/material/ImageListItem';
import ImageListItemBar from '@mui/material/ImageListItemBar';
import ListSubheader from '@mui/material/ListSubheader';
import IconButton from '@mui/material/IconButton';
import InfoIcon from '@mui/icons-material/Info';

export default function TitlebarImageList() {
  return (
    <ImageList sx={{ width: 500, height: 450 }}>
      <ImageListItem key="Subheader" cols={2}>
        <ListSubheader component="div">December</ListSubheader>
      </ImageListItem>
      {itemData.map((item) => (
        <ImageListItem key={item.img}>
          <img
            srcSet={`${item.img}?w=248&fit=crop&auto=format&dpr=2 2x`}
            src={`${item.img}?w=248&fit=crop&auto=format`}
            alt={item.title}
            loading="lazy"
          />
          <ImageListItemBar
            title={item.title}
            subtitle={item.author}
            actionIcon={
              <IconButton
                sx={{ color: 'rgba(255, 255, 255, 0.54)' }}
                aria-label={`info about ${item.title}`}
              >
                <InfoIcon />
              </IconButton>
            }
          />
        </ImageListItem>
      ))}
    </ImageList>
  );
}

(完整示例见 TitlebarImageList.tsx

ImageListItemBar 的关键 props(由 ImageListItemBar.js 中的 PropTypes 确认):

Prop 取值 默认值 说明
position 'below' | 'bottom' | 'top' 'bottom' 默认 bottom 为图片底部渐变遮罩
actionPosition 'left' | 'right' 'right' 操作图标的水平位置
title / subtitle node 标题与副标题,支持字符串或元素
actionIcon node 次要操作,通常是一个 IconButton

注意示例中 ImageListItem 默认 component 就是 li,这里额外用了一个跨 2 列的条目承载 ListSubheader,说明 ImageList 中放置任意非图片内容是合法的。

标题栏置于图片下方(position="below")

通过 position="below" 可将标题栏放在图片之外,此时标题栏使用 position: relative(源码中 below 分支切换了定位方式),不再作为绝对定位遮罩:

<ImageList sx={{ width: 500, height: 450 }}>
  {itemData.map((item) => (
    <ImageListItem key={item.img}>
      <img
        srcSet={`${item.img}?w=248&fit=crop&auto=format&dpr=2 2x`}
        src={`${item.img}?w=248&fit=crop&auto=format`}
        alt={item.title}
        loading="lazy"
      />
      <ImageListItemBar
        title={item.title}
        subtitle={<span>by: {item.author}</span>}
        position="below"
      />
    </ImageListItem>
  ))}
</ImageList>

(完整示例见 TitlebarBelowImageList.tsx;masonry 版本见 TitlebarBelowMasonryImageList.tsx,用法相同,仅需把外层换成 masonry 变体的 ImageList 并加滚动容器。)

定制图片列表:渐变标题栏、置顶位置与 gap

最后一个官方示例综合展示了 gap prop、position="top"actionPosition="left" 与自定义渐变 sx 背景:

import ImageList from '@mui/material/ImageList';
import ImageListItem from '@mui/material/ImageListItem';
import ImageListItemBar from '@mui/material/ImageListItemBar';
import IconButton from '@mui/material/IconButton';
import StarBorderIcon from '@mui/icons-material/StarBorder';

function srcset(image: string, width: number, height: number, rows = 1, cols = 1) {
  return {
    src: `${image}?w=${width * cols}&h=${height * rows}&fit=crop&auto=format`,
    srcSet: `${image}?w=${width * cols}&h=${height * rows}&fit=crop&auto=format&dpr=2 2x`,
  };
}

export default function CustomImageList() {
  return (
    <ImageList
      sx={{
        width: 500,
        height: 450,
        // Promote the list into its own layer in Chrome. This costs memory, but helps keeping high FPS.
        transform: 'translateZ(0)',
      }}
      rowHeight={200}
      gap={1}
    >
      {itemData.map((item) => {
        const cols = item.featured ? 2 : 1;
        const rows = item.featured ? 2 : 1;

        return (
          <ImageListItem key={item.img} cols={cols} rows={rows}>
            <img {...srcset(item.img, 250, 200, rows, cols)} alt={item.title} loading="lazy" />
            <ImageListItemBar
              sx={{
                background:
                  'linear-gradient(to bottom, rgba(0,0,0,0.7) 0%, ' +
                  'rgba(0,0,0,0.3) 70%, rgba(0,0,0,0) 100%)',
              }}
              title={item.title}
              position="top"
              actionIcon={
                <IconButton sx={{ color: 'white' }} aria-label={`star ${item.title}`}>
                  <StarBorderIcon />
                </IconButton>
              }
              actionPosition="left"
            />
          </ImageListItem>
        );
      })}
    </ImageList>
  );
}

(完整示例见 CustomImageList.tsx

其中值得注意的三处技巧:

  1. gap={1}:将默认 4px 的间距收窄到 1px,营造"照片墙"的紧凑感;
  2. transform: 'translateZ(0)':源码注释明确说明这是将列表提升为 Chrome 中的独立合成层,代价是内存,收益是滚动时保持高帧率——这是动画密集页面的常用手段;
  3. featured 数据驱动布局:数据中的 featured: true 条目动态计算 cols/rows 为 2,实现"精选大图 + 常规小图"混排,无需切换整个列表的 variant

源码级原理补充

Grid 布局与 Context 传递

ImageList.jsrowHeightgapvariant 打包为 Context 值下发:

const contextValue = React.useMemo(
  () => ({ rowHeight, gap, variant }),
  [rowHeight, gap, variant],
);

子组件 ImageListItem / ImageListItemBarImageListContext 读取这些值,据此计算自身尺寸与样式。这也解释了为什么 ImageListItemrows/cols 必须以父级 rowHeight 为基准才能算出正确像素高度。

类名与覆盖机制

两个组件均遵循 MUI 的 utility class 约定(imageListClasses.ts / imageListItemClasses.ts),类名含 MuiImageList-rootMuiImageList-standard 等变体类,配合 classes prop 或主题 components.MuiImageList.styleOverrides 即可在不改动结构的前提下覆盖任意位置(例如只改 positionTop 类来换标题栏配色)。

实践建议小结

  • 选择变体:条目均等用 standard;需要突出个别条目用 quilted(或 featured 动态跨格);追求节奏感用 woven;展示原始比例内容用 masonry
  • 图片尺寸:始终让 src/srcSet 请求的尺寸匹配 rowHeight 与跨格数,并加 dpr=2 的 2 倍图与 loading="lazy"
  • 标题栏位置:遮罩式信息用默认 bottom;文字需要完整可读性时用 position="below"(该模式下条目高度会包含标题栏,注意配合外层滚动容器);
  • 性能:长列表滚动时可用 translateZ(0) 提升合成层。

以上示例与组件行为均基于当前仓库中 image-list.md 文档、docs/data/material/components/image-list 下的官方示例及 packages/mui-material/src/ImageListImageListItemImageListItemBar 目录的源码实现,可直接在任意 @mui/material 项目中复制使用。

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