Material UI Progress 组件实战指南:CircularProgress 与 LinearProgress 全解析
本篇技术指南围绕 Material UI 的进度指示组件(Progress)展开,系统讲解 CircularProgress 与 LinearProgress 的四种 variant(indeterminate、determinate、buffer、query)、value/min/max/valueBuffer 等核心参数的使用方式,以及自定义样式、延迟出现与无障碍配置。读完本文,你将掌握在不同业务场景(加载等待、文件上传、缓冲预取)下选型并落地进度条的完整方案,并理解其动画背后的 CSS 实现机制与性能限制。
进度指示器(Progress indicators,通常俗称 spinner)用于表达未定长的等待时间或展示流程的完成长度,典型场景包括应用加载、表单提交、更新保存等。按语义分为两类:
- Determinate(确定型):显示操作还剩多久,需要真实进度值;
- Indeterminate(不确定型):表达一段无法预估的等待时间。
官方文档 progress.md 中还强调了一个设计前提:组件动画尽可能依赖 CSS 实现,这样即使在 JavaScript 加载完成之前,动画也能正常工作。
CircularProgress:环形进度
不确定型(默认形态)
CircularProgress 不传任何 variant 时默认渲染不确定型 spinner(源码中 variant 默认值为 'indeterminate',见 CircularProgress.js)。文档示例如下(示例源文件为 CircularIndeterminate.tsx):
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
export default function CircularIndeterminate() {
return (
<Box sx={{ display: 'flex' }}>
<CircularProgress aria-label="Loading…" />
</Box>
);
}
注意根元素渲染为带 role="progressbar" 的 span,因此必须通过 aria-label 或 aria-labelledby 提供可访问名称(详见“无障碍”一节)。
从源码结构看,不确定型动画由两个 CSS keyframes 驱动:circularRotateKeyframe 对整个根元素做 1.4s 线性旋转,circularDashKeyframe 则对内部 circle 做 1.4s 的 ease-in-out 收缩动画(CircularProgress.js)。这解释了文档“动画依赖 CSS”的表述——动画定义在样式层,与 React 渲染循环解耦。
颜色与尺寸
color 支持主题调色板中的任意命名色(primary、secondary、error、info、success、warning、inherit 等),默认为 'primary'。size 接受数字(按像素处理)或带单位的字符串(如 '3rem'),默认为 40;thickness 控制圆环粗细,默认为 3.6(见 CircularProgress.d.ts)。
尺寸示例(CircularSize.tsx)中即演示了 size={25}、size="3rem" 与 thickness={4} 的组合用法;颜色示例(CircularColor.tsx)则展示了 color="secondary" 等写法。
确定型与自定义刻度
指定操作加载进度时,使用 variant="determinate",并用 value 显示实际进度。示例(CircularDeterminate.tsx)展示了静态值 25/50/75/100 以及一个每 800ms 递增 10 的动态进度:
import * as React from 'react';
import Stack from '@mui/material/Stack';
import CircularProgress from '@mui/material/CircularProgress';
export default function CircularDeterminate() {
const [progress, setProgress] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10));
}, 800);
return () => {
clearInterval(timer);
};
}, []);
return (
<Stack spacing={2} direction="row">
<CircularProgress variant="determinate" value={25} aria-label="Export data" />
<CircularProgress variant="determinate" value={50} aria-label="Export data" />
<CircularProgress variant="determinate" value={75} aria-label="Export data" />
<CircularProgress variant="determinate" value={100} aria-label="Export data" />
<CircularProgress variant="determinate" value={progress} aria-label="Export data" />
</Stack>
);
}
默认情况下进度值期望落在 0–100 区间。通过 min 和 max 可以自定义刻度范围(min 默认 0、max 默认 100),例如电梯楼层这类非百分比场景:
<CircularProgress
variant="determinate"
min={10}
max={20}
value={progress}
aria-label="Loading"
/>
(对应示例 CircularCustomScale.tsx)
确定型的渲染原理值得深入一层:源码中内部 SVG 逻辑尺寸为常量 SIZE = 44,determinate 分支会按 2 * Math.PI * ((SIZE - thickness) / 2) 计算圆周长,再通过 stroke-dashoffset = ((max - value) / range) * circumference 把进度值映射为虚线偏移,并给根元素加 transform: rotate(-90deg) 使起点位于 12 点方向(CircularProgress.js)。开发模式下,若 min/max/value 不满足 min < max 且 min <= value <= max,会输出 console.error 警告;对 indeterminate 变体传入 min/max 也会收到“这些 props 不会生效”的警告(CircularProgress.js)。
常显轨道(enableTrackSlot)
默认环形进度没有背景轨道。传入 enableTrackSlot 属性会额外挂载一个 track slot,展示一圈半透明背景,且该轨道会复用 size 与 thickness 保持一致:
<CircularProgress enableTrackSlot size="30px" aria-label="Loading…" />
<CircularProgress enableTrackSlot variant="determinate" value={70} aria-label="Export data" />
(对应示例 CircularEnableTrack.tsx)
从源码看,track 是一个 stroke: currentColor 的 circle,透明度取 theme.palette.action.activatedOpacity(CircularProgress.js),并且带 aria-hidden="true" 避免辅助技术重复朗读。
与按钮、FAB 的交互集成
文档还给出了把 CircularProgress 与 Button、FAB 组合成“点击触发加载态”的集成示例(CircularIntegration.tsx),典型模式是:点击按钮后把 spinner 替换进按钮内容区,等待请求完成后恢复。
显示数值标签
把视觉进度值与环形组件叠加显示时,用绝对定位的 Typography 盖在 spinner 上即可。官方示例 CircularWithValueLabel.tsx 的写法:
<CircularProgress
variant="determinate"
size={70}
value={progress}
aria-label="Loading photos"
sx={{ position: 'absolute', left: '50%', ml: '-35px', top: '50%', mt: '-35px' }}
/>
即通过 left: 50% + 负 margin(半径)把圆形 spinner 精确居中到容器交点。
LinearProgress:条形进度
不确定型与 query 变体
LinearProgress 默认同样是不确定型进度条(LinearProgress.js)。其不确定动画由两段错峰的 left/right 位移 keyframes(indeterminate1Keyframe 与延迟 1.15s 的 indeterminate2Keyframe)组成,周期均为 2.1s(LinearProgress.js),这也是 Material Design 经典的“双条追逐”效果。
要反转不确定动画的滚动方向,使用 variant="query"。从源码结构看,query 与 indeterminate 共用两套 bar 动画,区别仅在于根元素额外应用了 transform: 'rotate(180deg)'(LinearProgress.js)——这正是“反转方向”的实现方式。示例见 LinearIndeterminate.tsx 与 LinearQuery.tsx。
颜色用法与 CircularProgress 一致,color="primary"(默认)与 color="secondary" 之外还支持任意主题色,见 LinearColor.tsx。
确定型与 buffer 缓冲变体
variant="determinate" 加 value 即显示实际进度(LinearDeterminate.tsx)。variant="buffer" 则额外用 valueBuffer 显示一段“缓冲进度”,官方文档明确要求 valueBuffer 必须大于 value:
<LinearProgress
variant="buffer"
value={progress}
valueBuffer={buffer}
aria-label="Loading…"
/>
示例 LinearBuffer.tsx 模拟了视频缓冲:真实进度每 100ms 加 1,缓冲值按随机步长领先增长,到达 100 后双双复位。
buffer 变体的渲染结构可以结合源码理解:根元素内最多渲染三层——Dashed(点状缓冲纹理层,带 3s 的 bufferKeyframe 透明度/背景位移动画)、Bar1(真实进度条)、Bar2(缓冲进度条,颜色取调色板色加亮 62% 的浅色调,见 getColorShade 函数,LinearProgress.js)。进度条的位移由 translateX(((value - min) / range) * 100 - 100%) 计算,且 RTL 环境下会自动取反(LinearProgress.js)。
开发模式的参数校验同样严格:
determinate/buffer变体缺少value:报错 “You need to provide a value prop…”;buffer变体缺少valueBuffer:报错 “You need to provide a valueBuffer prop…”;min/max/value/valueBuffer不满足min < max且min <= value <= valueBuffer <= max:报错并给出实际收到的数值;- 对
indeterminate/query变体传入min/max:警告“这些 props 不会生效”。
(校验逻辑见 LinearProgress.js)
数值标签与自定义 aria-valuetext
进度 value 也可以显示在进度条旁边(LinearWithValueLabel.tsx),通常用一个 LinearProgressWithLabel 包装组件同时渲染 Typography 与 LinearProgress。
当进度值不是百分比语义时(例如楼层数),默认辅助技术会按百分比朗读 aria-valuenow,此时应使用 aria-valuetext 提供准确描述。官方示例 LinearWithAriaValueText.tsx 的模式值得直接借鉴:
function LinearProgressWithLabelAndValue({ max, min, value, ...rest }) {
const progressText = `Elevator at floor ${value} out of ${max}.`;
const progressId = React.useId();
return (
<div>
<Typography id={progressId} variant="body2" color="text.secondary" sx={{ mr: 1 }}>
Elevator status
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress
variant="determinate"
aria-labelledby={progressId}
aria-valuetext={progressText}
min={min}
max={max}
value={value}
{...rest}
/>
</Box>
<Box sx={{ whiteSpace: 'nowrap' }}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{progressText}
</Typography>
</Box>
</Box>
</div>
);
}
注意 aria-labelledby 指向可见文字标签的 id(这里用 React.useId() 生成,避免多实例冲突),aria-valuetext 承载完整语义。
自定义样式
官方文档的 Customization 章节(对应示例 CustomizedProgressBars.tsx)给出了三类典型定制,覆盖文档页 customization 所讲的 overrides 思路:
- 仿 Facebook 风格的圆形 spinner:用
disableShrink关闭收缩动画、enableTrackSlot显示轨道、animationDuration: '550ms'加快旋转,并通过sx精确选择circularProgressClasses.circle(strokeLinecap: 'round')与circularProgressClasses.track(灰色描边)两个 slot:
<CircularProgress
variant="indeterminate"
disableShrink
enableTrackSlot
sx={(theme) => ({
color: '#1a90ff',
animationDuration: '550ms',
[`& .${circularProgressClasses.circle}`]: {
strokeLinecap: 'round',
},
[`& .${circularProgressClasses.track}`]: {
opacity: 1,
stroke: (theme.vars || theme).palette.grey[200],
},
})}
size={40}
thickness={4}
aria-label="Loading…"
/>
-
渐变圆形进度:先渲染一个 0 尺寸
<svg>定义<linearGradient id="my_gradient">,再用sx={{ 'svg circle': { stroke: 'url(#my_gradient)' } }}把描边替换为渐变。 -
带圆角的条形进度:用
styled(LinearProgress)覆盖height/borderRadius,并通过linearProgressClasses.colorPrimary与linearProgressClasses.bar分别控制轨道底色(含深色模式分支)和进度条颜色:
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
borderRadius: 5,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: theme.palette.grey[200],
...theme.applyStyles('dark', { backgroundColor: theme.palette.grey[800] }),
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 5,
backgroundColor: '#1a90ff',
...theme.applyStyles('dark', { backgroundColor: '#308fe8' }),
},
}));
这些工具类名由 circularProgressClasses.ts 与 linearProgressClasses.ts 统一生成:Circular 提供 root、svg、track、circle、circleDisableShrink 等 key;Linear 提供 root、dashed、bar、bar1、bar2 及各 variant/color 组合 key,定制时应优先使用这些稳定类名而非自行选择 DOM。
延迟出现:1 秒响应阈值
文档指出,围绕响应时间有 3 个重要阈值。ButtonBase 的涟漪效果让用户感觉 UI 即时响应;通常 0.1s–1.0s 之间的延迟不需要额外反馈;超过 1.0s 后应显示 loader 以维持用户的思考流不断。因此最佳实践是“先等约 1 秒,确认任务确实耗时后再弹出进度条”。
官方示例 DelayingAppearance.tsx 用 Fade 过渡实现延迟:
<Fade
in={loading}
style={{ transitionDelay: loading ? '800ms' : '0ms' }}
unmountOnExit
>
<CircularProgress aria-label="Loading…" />
</Fade>
关键点:出现时给 transitionDelay 设 800ms(若加载很快则 spinner 始终不出现),消失时延迟设为 0ms(立即收起),unmountOnExit 保证不占用布局空间。示例中第二个按钮进一步模拟了“查询”场景:点击后进入 progress 状态延迟 800ms 出现 spinner,2 秒后转为 success 并展示 “Success!” 文本。
无障碍要求
进度条必须通过以下两种方式之一获得可访问名称:
- 设置
aria-labelledby,指向可见文本标签的id(推荐,标签同时向视觉用户解释进度含义); - 或使用
aria-label属性。
组件源码中,determinate/buffer 变体还会自动输出 aria-valuenow、aria-valuemin、aria-valuemax(CircularProgress.js、LinearProgress.js)。组件类型定义中另补充了一条区域级建议:当进度条描述的是页面某个区域的加载状态时,应在该区域上设置 aria-busy="true",并用 aria-describedby 指向该进度条,加载完成后再移除。
已知限制与规避方案
高 CPU 负载下的动画异常
文档明确警告:在主线程承受重负载时,可能丢失 stroke dash 收缩动画,或看到 CircularProgress 环宽随机抖动(文档中附有演示视频)。原因是动画依赖合成器与主线程的协作,而重计算会阻塞渲染。官方建议是把 CPU 密集操作放入 Web Worker 或分批执行,避免阻塞主渲染线程。
当确实无法做到时,可以用 disableShrink 属性缓解问题——它直接移除收缩动画,只保留旋转动画(源码中 disableShrink 仅在 variant="indeterminate" 时生效,对其他变体传入会收到 PropTypes 警告,见 CircularProgress.js):
<CircularProgress disableShrink aria-label="Loading…" />
(对应示例 CircularUnderLoad.tsx)
高频更新的 200ms 延迟
LinearProgress 使用 CSS transform 的过渡来平滑不同 value 之间的变化,默认过渡时长为 200ms。如果父组件更新 value 的频率过快,渲染更新与进度条完全到位之间至少会存在 200ms 的视觉延迟。
若需要达到每秒 30 次以上重渲染(约 33ms/帧),文档建议直接禁用过渡:
.MuiLinearProgress-bar {
transition: none;
}
这一点与源码吻合:bar1/bar2 的基础样式中由 getTransitionStyles(theme, 'transform', { duration: '0.2s', easing: 'linear' }) 注入了 0.2s 的 transform 过渡(LinearProgress.js),而 determinate/buffer 变体又用 .${TRANSITION_DURATION}s(即 0.4s 的 TRANSITION_DURATION = 4 秒级数值拼出)覆盖——高频数据流场景下,通过全局 CSS 或 sx 移除该 transition 是最直接的取舍。
小结
Material UI 的 Progress 组件族以 CSS 动画为底座、以 variant 参数为语义开关,覆盖了“等待”(indeterminate)、“进行中”(determinate)、“预取缓冲”(buffer)、“反向查询”(query)四类状态。实际项目中的关键决策点可以归纳为:
| 场景 | 组件 / variant | 关键 props |
|---|---|---|
| 未知时长的加载等待 | CircularProgress / LinearProgress | variant="indeterminate"(默认) |
| 有真实进度的操作 | CircularProgress / LinearProgress | variant="determinate" + value(配合 min/max 自定义刻度) |
| 视频/文件缓冲 | LinearProgress | variant="buffer" + value + valueBuffer(须 valueBuffer > value) |
| 常量轨道的 spinner | CircularProgress | enableTrackSlot |
| 高负载环境 | CircularProgress | disableShrink |
| 快于 30 帧/秒的更新 | LinearProgress | 覆盖 .MuiLinearProgress-bar { transition: none; } |
配合“延迟 1 秒再显示”的交互策略与 aria-label/aria-labelledby/aria-valuetext 的无障碍配置,即可在任意 React 应用中落地既流畅又合规的进度反馈。所有示例代码均可在仓库 docs/data/material/components/progress/ 目录中逐一对应到源文件,组件实现位于 packages/mui-material/src/CircularProgress/ 与 packages/mui-material/src/LinearProgress/,其单元测试分别位于 CircularProgress.test.js 与 LinearProgress.test.js。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00