首页
/ Material UI(@mui/lab)Timeline 时间线组件详解:从基础布局到位置系统与源码实现

Material UI(@mui/lab)Timeline 时间线组件详解:从基础布局到位置系统与源码实现

2026-09-04 22:26:50作者:翟萌耘Ralph

本文围绕 Material UI 实验室包 @mui/lab 中的 Timeline 组件族展开,完整覆盖文档页 docs/data/material/components/timeline/timeline.md 中的全部示例场景(基础时间线、左侧定位、交替布局、颜色与轮廓变体、对侧内容、深度定制与容器内对齐),并结合 packages/mui-lab/src 下的源码实现,讲清 position 属性如何通过 Context 贯穿六个子组件、各默认值与类名规则从何而来,帮助读者既能直接复制可运行的示例,也能理解其底层布局机制。

一、Timeline 是什么,以及它属于哪个包

时间线(Timeline)组件用于按时间顺序展示一组事件(the timeline displays a list of events in chronological order)。文档中特别注明了一个事实:该组件并未出现在 Google 的 Material Design 官方指南中,但 Material UI 提供了它,并放置在实验性包中:

安装后按子路径导入即可:

npm install @mui/lab @mui/material @emotion/react @emotion/styled
import Timeline from '@mui/lab/Timeline';
import TimelineItem from '@mui/lab/TimelineItem';
import TimelineSeparator from '@mui/lab/TimelineSeparator';
import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent';
import TimelineDot from '@mui/lab/TimelineDot';
import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent';

由于 Timeline 位于 @mui/lab,从源码结构看它属于实验性 API,接口在正式版中仍可能演进,生产使用前建议关注版本说明。

二、基础时间线:最小可用结构

文档的第一节 "Basic timeline" 展示了最简单的用法(对应 BasicTimeline.tsx)。其结构规律是:Timeline(容器)→ TimelineItem(单个事件)→ TimelineSeparator(中轴分隔区)+ TimelineContent(事件内容),其中 Separator 内再嵌套 Dot 与 Connector:

import Timeline from '@mui/lab/Timeline';
import TimelineItem from '@mui/lab/TimelineItem';
import TimelineSeparator from '@mui/lab/TimelineSeparator';
import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent';
import TimelineDot from '@mui/lab/TimelineDot';

export default function BasicTimeline() {
  return (
    <Timeline>
      <TimelineItem>
        <TimelineSeparator>
          <TimelineDot />
          <TimelineConnector />
        </TimelineSeparator>
        <TimelineContent>Eat</TimelineContent>
      </TimelineItem>
      <TimelineItem>
        <TimelineSeparator>
          <TimelineDot />
          <TimelineConnector />
        </TimelineSeparator>
        <TimelineContent>Code</TimelineContent>
      </TimelineItem>
      <TimelineItem>
        <TimelineSeparator>
          <TimelineDot />
        </TimelineSeparator>
        <TimelineContent>Sleep</TimelineContent>
      </TimelineItem>
    </Timeline>
  );
}

注意细节:

  1. 最后一个 TimelineItem 不写 TimelineConnector——Connector 是连接当前 Dot 与下一个 Dot 的竖线,末尾无下一条事件,故省略。
  2. Timeline.tsx 看,Timeline 的根元素是 <ul>styled('ul', ...)),TimelineItem 的根元素是 <li>styled('li', ...),见 TimelineItem.js),语义上构成一个真正的无序列表。根样式为 display: flex; flex-direction: column; padding: 6px 16px; flex-grow: 1,即纵向堆叠、自身撑满父容器宽度——这是后文"默认在容器内居中"这一行为的来源。
  3. TimelineConnector源码 看是一条固定 2px 宽、默认取主题 palette.grey[400] 背景色、并设置 flexGrow: 1 的竖线,因此它会自动占据 Dot 之间的剩余高度,不需要手动指定高度

三、position 属性:内容相对时间轴的四种定位

Timeline 组件唯一的功能型属性是 position,它决定 TimelineContent 相对于中轴(时间轴)出现在哪一侧。类型定义(Timeline.types.ts)与 PropTypes(Timeline.tsx)给出取值与默认值:

取值 说明
'right' 默认值(@default 'right'),内容显示在时间轴右侧
'left' 内容显示在时间轴左侧
'alternate' 内容在左右两侧交替显示,第一个事件在右
'alternate-reverse' 交替显示,但顺序反转,第一个事件在左

3.1 左侧定位时间线

文档 "Left-positioned timeline" 小节说明:主内容可以相对时间轴定位在左侧,只需给容器加 position="left"(完整示例见 LeftPositionedTimeline.tsx):

<Timeline position="left">
  {/* TimelineItem 结构同基础示例 */}
</Timeline>

源码中这一属性的作用路径是:

  • Timeline 组件把 position 默认值归一化后写入 TimelineContextconst { position = 'right', ... } = propsTimeline.tsx);
  • 每个 TimelineItem 通过 React.useContext(TimelineContext) 读取该值,允许单个 item 用自身 position 属性覆盖容器级设置,并再次向下传递(TimelineItem.js);
  • TimelineContentTimelineOppositeContent 同样消费该 Context,并据此翻转文本对齐方式:TimelineContentposition === 'left'textAlign: 'right'TimelineOppositeContentposition === 'left'textAlign: 'left'TimelineContent.jsTimelineOppositeContent.js)。

也就是说,position 是沿着 Timeline → TimelineItem → Content/OppositeContent 的 Context 链逐层流动的,任何一层缺省都会沿链向上继承,最终兜底到 'right'

3.2 交替时间线

文档 "Alternating timeline" 与 "Reverse Alternating timeline" 两节分别演示 position="alternate"position="alternate-reverse"(示例见 AlternateTimeline.tsxAlternateReverseTimeline.tsx),让事件在左右两侧交替出现。从源码看实现非常克制,全部靠 CSS 选择器完成(TimelineItem.js):

...((ownerState.position === 'alternate' || ownerState.position === 'alternate-reverse') && {
  [`&:nth-of-type(${ownerState.position === 'alternate' ? 'even' : 'odd'})`]: {
    flexDirection: 'row-reverse',
    [`& .${timelineContentClasses.root}`]: {
      textAlign: 'right',
    },
    [`& .${timelineOppositeContentClasses.root}`]: {
      textAlign: 'left',
    },
  },
}),

即:alternate 时把偶数项、alternate-reverse 时把奇数项的 flexDirection 反转为 row-reverse,并同步翻转两侧内容的对齐方向,JS 逻辑中没有任何索引运算。

3.3 position 与类名的对应关系

每个位置值都会生成一个稳定的工具类,映射函数 convertTimelinePositionToClass'alternate-reverse' 特判为 positionAlternateReverse,其余按首字母大写拼接(positionLeft / positionRight / positionAlternate)。完整类名清单见 timelineClasses.tsrootpositionLeftpositionRightpositionAlternatepositionAlternateReverse

该行为有测试保障:Timeline.test.tsx 中验证了 position="alternate-reverse" 的根元素会带有 MuiTimeline-positionAlternateReverse 类名,且组件遵循 ul 继承、ref 类型等规范。

四、TimelineDot:颜色与 variant 变体

文档 "Color" 与 "Outlined" 两节展示 Dot 的两种视觉维度,二者都由 TimelineDot 的属性控制。

4.1 颜色(color)

TimelineDot 支持取自主题调色板的颜色(示例 ColorsTimeline.tsx):

<Timeline position="alternate">
  <TimelineItem>
    <TimelineSeparator>
      <TimelineDot color="secondary" />
      <TimelineConnector />
    </TimelineSeparator>
    <TimelineContent>Secondary</TimelineContent>
  </TimelineItem>
  <TimelineItem>
    <TimelineSeparator>
      <TimelineDot color="success" />
    </TimelineSeparator>
    <TimelineContent>Success</TimelineContent>
  </TimelineItem>
</Timeline>

TimelineDot.js 的 PropTypes 看,color 默认 'grey',可选枚举值为 'error' | 'grey' | 'info' | 'inherit' | 'primary' | 'secondary' | 'success' | 'warning'(PropTypes 同时宽松接受任意字符串)。样式逻辑上:

  • variant="filled"(默认):取 palette[color].main 作为背景、palette[color].contrastText 作为文字色;grey 走特例(grey[500] 背景 + grey[400] 相关配色);
  • color="inherit":不附加颜色相关类与内联样式,完全交给外部 sx/classes 控制。

4.2 轮廓变体(variant)

"Outlined" 一节(示例 OutlinedTimeline.tsx)把 variant="outlined" 与颜色组合:

<Timeline position="alternate">
  <TimelineItem>
    <TimelineSeparator>
      <TimelineDot variant="outlined" />
      <TimelineConnector />
    </TimelineSeparator>
    <TimelineContent>Eat</TimelineContent>
  </TimelineItem>
  <TimelineItem>
    <TimelineSeparator>
      <TimelineDot variant="outlined" color="primary" />
      <TimelineConnector />
    </TimelineSeparator>
    <TimelineContent>Code</TimelineContent>
  </TimelineItem>
  <TimelineItem>
    <TimelineSeparator>
      <TimelineDot variant="outlined" color="secondary" />
      <TimelineConnector />
    </TimelineSeparator>
    <TimelineContent>Sleep</TimelineContent>
  </TimelineItem>
  <TimelineItem>
    <TimelineSeparator>
      <TimelineDot variant="outlined" />
    </TimelineSeparator>
    <TimelineContent>Repeat</TimelineContent>
  </TimelineItem>
</Timeline>

variant 默认 'filled'。从源码看两种变体的差异:filled 有 2px 边框(borderColor: 'transparent')、圆角 50%、4px 内边距、shadows[1] 阴影;outlined 则去掉阴影(boxShadow: 'none')、背景透明,仅保留主题色边框。每个 variant + color 组合还会生成独立工具类(如 filledPrimaryoutlinedSecondary,由 useUtilityClasses${variant}${capitalize(color)} 拼出),便于通过 classes 精确定制。

五、TimelineOppositeContent:对侧内容

文档 "Opposite content" 小节说明:时间线可以在中轴的另一侧显示内容,典型用途是展示时间戳(示例 OppositeContentTimeline.tsx):

import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent';

<Timeline position="alternate">
  <TimelineItem>
    <TimelineOppositeContent
      sx={{
        color: 'text.secondary',
      }}
    >
      09:30 am
    </TimelineOppositeContent>
    <TimelineSeparator>
      <TimelineDot />
      <TimelineConnector />
    </TimelineSeparator>
    <TimelineContent>Eat</TimelineContent>
  </TimelineItem>
  {/* 其余 item 同构:09:30 / 10:00 / 12:00 / 9:00 am */}
</Timeline>

TimelineOppositeContent 同样基于 Typographystyled(Typography, ...)),默认样式为 padding: 6px 16px; margin-right: auto; text-align: right; flex: 1TimelineOppositeContent.js)。

这里有一个值得注意的联动设计:TimelineItem 在渲染前会遍历子节点,检查是否存在 TimelineOppositeContent(用 isMuiElement(child, ['TimelineOppositeContent']) 判断,见 TimelineItem.js)。若没有对侧内容,item 会通过 ::before 伪元素插入一个 flex: 1 的占位块(missingOppositeContent 状态类),把内容区保持在中间位置:

[`&:where(:not(:has(.${timelineOppositeContentClasses.root})))::before`]: {
  content: '""',
  flex: 1,
  padding: '6px 16px',
},

从源码注释看,该选择器特意用 :where() 压住特异性到 (0,1,1),以便用户对 ::before 的自定义覆盖能胜出——这正是下面 "Left-aligned with no opposite content" 示例成立的前提。

六、深度定制:图标、标题、时间轴染色

文档 "Customization" 一节给出官方定制示例(CustomizedTimeline.tsx),要点可归纳为四类技巧,官方文档建议配合 样式覆盖文档 进一步学习:

  1. 把图标放进 DotTimelineDot 接受 children,直接包一层 @mui/icons-material 图标(FastfoodIconLaptopMacIcon 等),Dot 的圆角、内边距与阴影会自动包裹图标;
  2. 富文本内容TimelineContent 基于 Typography,内部可放多段 Typography(标题 + 描述),并用 sx={{ py: '12px', px: 2 }} 调整内边距;
  3. 时间轴染色TimelineConnector 支持 sx={{ bgcolor: 'secondary.main' }} 改变竖线颜色;
  4. 对侧内容的排版TimelineOppositeContent 可使用 alignvariant="body2" 等 Typography 属性。

完整示例(节选一个 item):

<TimelineItem>
  <TimelineOppositeContent
    align="right"
    variant="body2"
    sx={{
      color: 'text.secondary',
      m: 'auto 0',
    }}
  >
    9:30 am
  </TimelineOppositeContent>
  <TimelineSeparator>
    <TimelineConnector />
    <TimelineDot>
      <FastfoodIcon />
    </TimelineDot>
    <TimelineConnector />
  </TimelineSeparator>
  <TimelineContent sx={{ py: '12px', px: 2 }}>
    <Typography variant="h6" component="span">
      Eat
    </Typography>
    <Typography>Because you need strength</Typography>
  </TimelineContent>
</TimelineItem>

七、容器内对齐:利用 flex 权重调整左右比例

文档 "Alignment" 一节说明:Timeline 有多种在容器内摆放的方式,可通过样式覆盖实现;Timeline 默认在容器内居中。三个示例分别演示如何调整左右两侧的比例(示例源码 LeftAlignedTimeline.tsxRightAlignedTimeline.tsxNoOppositeContent.tsx)。

7.1 左对齐

压缩对侧内容(时间戳列)的 flex 权重到 0.2,主内容区自然占据剩余空间,整体视觉上"左对齐":

import TimelineOppositeContent, {
  timelineOppositeContentClasses,
} from '@mui/lab/TimelineOppositeContent';

<Timeline
  sx={{
    [`& .${timelineOppositeContentClasses.root}`]: {
      flex: 0.2,
    },
  }}
>
  <TimelineItem>
    <TimelineOppositeContent color="textSecondary">09:30 am</TimelineOppositeContent>
    <TimelineSeparator>
      <TimelineDot />
      <TimelineConnector />
    </TimelineSeparator>
    <TimelineContent>Eat</TimelineContent>
  </TimelineItem>
</Timeline>

7.2 右对齐

对称地,压缩主内容区(timelineContentClasses.root)的权重:

import TimelineContent, { timelineContentClasses } from '@mui/lab/TimelineContent';

<Timeline
  sx={{
    [`& .${timelineContentClasses.root}`]: {
      flex: 0.2,
    },
  }}
>
  {/* items 同上 */}
</Timeline>

7.3 无对侧内容时的左对齐

当 item 没有 TimelineOppositeContent 时,::before 占位伪元素(flex: 1)会把内容推到中间;此时把该伪元素的 flexpadding 都归零即可实现左对齐:

import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';

<Timeline
  sx={{
    [`& .${timelineItemClasses.root}:before`]: {
      flex: 0,
      padding: 0,
    },
  }}
>
  {/* 无 TimelineOppositeContent 的 items */}
</Timeline>

这类调整之所以可行,正是因为每个部分都导出了稳定的类名常量(timelineContentClassestimelineOppositeContentClassestimelineItemClasses 等),而 Timeline 根元素的 flexGrow: 1Timeline.tsx)保证了整个时间线会先撑满父容器、再由内部 flex 权重分配左右比例。

八、组件结构与定制入口速查

结合源码与文档示例,各部分的职责与定制入口如下表:

组件 根元素 默认样式要点(源码依据) 关键属性
Timeline ul display: flex; flex-direction: column; padding: 6px 16px; flex-grow: 1 position(默认 'right')、sxclasses
TimelineItem li display: flex; min-height: 70px; position: relative;无对侧内容时 ::before 占位 position(可覆盖容器值)、sx
TimelineSeparator div display: flex; flex-direction: column; align-items: center; flex: 0 sx(如 bgcolor 染线)
TimelineDot span 2px 边框、border-radius: 50%padding: 4shadows[1] color(默认 'grey')、variant'filled' / 'outlined')、children(图标)
TimelineConnector span width: 2; background: palette.grey[400]; flex-grow: 1 sx
TimelineContent div(基于 Typography flex: 1; padding: 6px 16pxposition="left" 时右对齐 variantalign 等 Typography 属性
TimelineOppositeContent div(基于 Typography padding: 6px 16px; margin-right: auto; text-align: right; flex: 1 同上

定制有三个统一入口,与 Material UI 全局约定一致:

  1. sx 属性:所有七个组件都接受(源码 PropTypes 中均有 sx 声明);
  2. classes 属性 + 各部分导出的 *Classes 常量:精确命中某个部分,例如上文对齐示例;
  3. 主题 components.MuiTimeline 等 overrides:通过 styled 组件声明的 overridesResolver(如 Timeline.tsx)把 position 状态类并入覆盖链,因此主题层也能按位置分别覆写。

九、小结

@mui/lab 的 Timeline 是一套"少属性、强组合"的组件族:布局变化全部由容器一个 position 属性驱动,并通过 React Context 逐层下发;视觉变体集中在 TimelineDotcolor / variant 两个属性;而更细的排版(对齐、占比、染色)则依托稳定的工具类常量与 sx 完成。配合仓库中 timeline.md 的九个官方示例(BasicTimelineLeftPositionedTimelineAlternateTimelineAlternateReverseTimelineColorsTimelineOutlinedTimelineOppositeContentTimelineCustomizedTimeline、对齐三例)与 源码实现,基本可以覆盖事件日志、部署记录、履历展示等常见时间线场景的落地需求。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384