Material UI Skeleton 组件深度解析:骨架屏原理、变体、动画与尺寸推断
Skeleton(骨架屏)是 Material UI(MUI)用于在数据加载完成前展示内容占位预览的组件,通过模拟最终页面布局来降低用户等待时的挫败感,提升页面的感知响应速度。阅读本文后,你将掌握 Skeleton 组件的四种形状变体(text/circular/rectangular/rounded)、三种动画模式(pulse/wave/关闭)的完整用法,理解其尺寸推断(children 推断与排版推断)的底层实现机制,并能从源码层面解释骨架屏颜色、圆角、动画帧的实现原理与无障碍注意事项。
使用场景:用骨架屏替代加载旋转器
组件数据可能无法立即可用。在数据到达之前渲染一个与最终布局形状一致的占位块,能让用户感觉"事情正在立刻发生",信息随后逐步呈现到屏幕上。官方文档 skeleton.md 明确指出,骨架屏是传统 spinner(旋转加载器)的替代方案:它不展示抽象的动画小部件,而是通过"预示即将出现的内容"来降低认知负载。
该组件被设计为直接内嵌在你的业务组件中使用,典型的加载态切换写法如下(源自文档 Usage 章节):
{
item ? (
<img
style={{
width: 210,
height: 118,
}}
alt={item.title}
src={item.src}
/>
) : (
<Skeleton variant="rectangular" width={210} height={118} />
);
}
更完整的实战示例可以参考仓库中的 YouTube 卡片加载演示 YouTube.tsx:它用 loading ? Array.from(new Array(3)) : data 构造一个与真实数据等长的数组,加载时渲染 <Skeleton variant="rectangular" width={210} height={118} /> 加两行文字骨架(<Skeleton /> 与 <Skeleton width="60%" />),数据到达后无缝切换为 img + Typography,占位区域与内容区域尺寸完全一致,避免了布局跳动(CLS)。
四种形状变体(variant)
组件支持 4 种形状变体,由 variant prop 控制(默认 text),类型定义为 OverridableStringUnion<'text' | 'rectangular' | 'rounded' | 'circular', ...>,见 Skeleton.d.ts:
text(默认):表示单行文本。高度不是靠height控制,而是通过字号(font size)调整——在排版上下文中它使用em单位自适应父级字体;circular:正圆(头像等场景);rectangular:直角矩形,完全无圆角,尺寸由你接管;rounded:带主题圆角的矩形,尺寸同样由你控制。
官方示例 Variants.tsx 展示了四种变体的标准写法:
<Stack spacing={1}>
{/* For variant="text", adjust the height via font-size */}
<Skeleton variant="text" sx={{ fontSize: '1rem' }} />
{/* For other variants, adjust the size with `width` and `height` */}
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={210} height={60} />
<Skeleton variant="rounded" width={210} height={60} />
</Stack>
源码中的变体样式实现
从源码结构看,四种变体分别映射为 CSS 变体(variants),定义在 Skeleton.js:
- text:
height: 'auto',配合transform: scale(1, 0.60)纵向压缩为一条文本条;圆角通过主题shape.borderRadius计算为radius / 0.6(补偿纵向压缩后的视觉比例),并且&:empty:before { content: '"\00a0"' }用一个不间断空格保证空span也有渲染高度。这也是为什么文档说 text 变体"通过字号调整高度"——它继承父级font-size,基准高度为1.2em(见 Skeleton.js 第 111 行); - circular:
borderRadius: '50%'; - rounded:
borderRadius取主题shape.borderRadius; - rectangular:无额外圆角样式(默认 0)。
每个变体对应一个 utility class(MuiSkeleton-text、MuiSkeleton-circular 等),由 skeletonClasses.ts 统一生成,可通过 classes prop 覆盖。
动画模式(animation)
默认情况下骨架屏做**脉冲(pulsate)**动画,也可以改为波浪(wave)或完全关闭动画。示例 Animations.tsx:
<Box sx={{ width: 300 }}>
<Skeleton /> {/* 默认 pulse */}
<Skeleton animation="wave" />
<Skeleton animation={false} />
</Box>
animation prop 的取值类型为 'pulse' | 'wave' | false(见 Skeleton.d.ts 第 11-16 行)。
脉冲动画实现
pulse 是一个 2 秒的透明度往复关键帧动画,源码中的关键帧定义在 Skeleton.js 第 30-42 行:
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
/* animation: 2s ease-in-out 0.5s infinite */
即骨架块在 0.5 秒延迟后,以 ease-in-out 曲线无限循环地在完全不透明与 40% 透明度之间往复。
波浪动画实现
wave 动画的实现更巧妙:它在根元素上设置 position: relative; overflow: hidden,并通过 ::after 伪元素铺设一条从左到右扫过的横向渐变光带(渐变色取 theme.palette.action.hover)。关键帧定义在 Skeleton.js 第 44-57 行:
@keyframes wave {
0% { transform: translateX(-100%); }
50% { transform: translateX(100%); } /* +0.5s of delay between each loop */
100% { transform: translateX(100%); }
}
细节上有两个工程考量(见 Skeleton.js 第 185-209 行):
WebkitMaskImage: '-webkit-radial-gradient(white, black)'用于修复 Safari 的一个渲染 bug;::after初始transform: translateX(-100%),注释标明目的是"Avoid flash during server-side hydration"——避免服务端渲染首帧时出现闪烁。
Facebook 风格卡片演示 Facebook.tsx 展示了 wave 动画在真实卡片中的综合运用:头像位置用 <Skeleton animation="wave" variant="circular" width={40} height={40} />,标题/副标题用 height={10} 的细条,正文媒体区用 <Skeleton sx={{ height: 190 }} animation="wave" variant="rectangular" />。
减少动态效果(reduced motion)支持
源码中通过 getReducedMotionStyles(来自 transitions/utils)读取主题的 transitions.disable 设置:当用户偏好减少动态效果时,pulse 会置为 animation: none,wave 则会同时关闭 ::after 的动画并隐藏光带(见 Skeleton.js 第 97-103 行)。这意味着骨架屏动画会自动尊重操作系统的"减弱动态效果"偏好。
尺寸推断:width/height 之外的两种取巧方式
除 width 和 height prop(支持 number | string,最终写入内联 style,见 Skeleton.js 第 266-270 行)外,组件还支持两种尺寸推断。
从排版推断(typography-based sizing)
对文本场景,由于 text 变体高度用 em 单位表达,骨架可以直接作为 Typography 的子元素,高度自动跟随排版规格:
<Typography variant="h1">{loading ? <Skeleton /> : 'h1'}</Typography>
演示 SkeletonTypography.tsx 对 h1、h3、body1、caption 四个规格做了对比:加载态渲染 <Skeleton />,完成态渲染规格名,可以直观看到骨架条高度随字号逐级变化。文档同时提醒:从排版推断尺寸时应保持 variant="text"(这正是默认值),如果只需要换形状而保留排版尺寸,用 sx 微调 borderRadius 即可,而不是改用其他变体。
从 children 推断(children-based sizing)
对其他组件,你可能不想重复声明宽高。此时把真实组件作为 children 传入,Skeleton 会渲染它并据此推断宽高——子元素本身会被隐藏:
loading ? (
<Skeleton variant="circular">
<Avatar />
</Skeleton>
) : (
<Avatar src={data.avatar} />
);
演示 SkeletonChildren.tsx 还展示了两种实用技巧:
// 宽度撑满父容器:
<Skeleton width="100%">
<Typography>.</Typography>
</Skeleton>
// 用 padding-top 比例占位符推断图片宽高比(16:9):
<Skeleton variant="rectangular" width="100%">
<div style={{ paddingTop: '57%' }} />
</Skeleton>
源码中的 children 推断机制
从源码结构看,children 推断由三组条件样式协同完成(Skeleton.js 第 147-166 行),并以 hasChildren(即 Boolean(other.children),见 第 254 行)作为开关:
& > * { visibility: 'hidden' }:子元素照常参与布局(撑起宽高),但视觉上不可见;- 有 children 且未指定
width时 →maxWidth: 'fit-content',让骨架宽度贴合内容; - 有 children 且未指定
height时 →height: 'auto',覆盖默认的1.2em基准高度。
对应的 utility classes 是 withChildren、fitContent、heightAuto(skeletonClasses.ts 第 19-24 行)。Skeleton.test.js 中的用例(如 width="100" + children、height="100" + children 的组合)验证了 prop 显式指定时会覆盖推断行为。
注意一个语义细节:width/height 是写在内联 style 上的,优先级高于推断样式。因此 <Skeleton width="100%"><Typography>.</Typography></Skeleton> 中骨架宽度是 100% 而非内容宽度。
颜色定制与深色背景
骨架屏颜色可以通过修改 background-color CSS 属性定制(官方推荐用 sx prop),在黑色背景上尤其有用,否则骨架屏会"隐形"。示例 SkeletonColor.tsx:
<Box sx={{ bgcolor: '#121212', p: 8, width: '100%', display: 'flex', justifyContent: 'center' }}>
<Skeleton
sx={{ bgcolor: 'grey.900' }}
variant="rectangular"
width={210}
height={118}
/>
</Box>
从源码看默认颜色并非写死的灰色:在 CSS 变量主题下取 theme.vars.palette.Skeleton.bg,否则由主文本色按模式混合得到——亮色模式 theme.alpha(theme.palette.text.primary, 0.11),暗色模式 0.13(见 Skeleton.js 第 107-110 行)。
无障碍(Accessibility)
官方文档 skeleton.md 的 Accessibility 章节给出了三条明确结论:
- ARIA:无。骨架屏不附加任何 ARIA 属性,因为它只是视觉占位,不承载内容语义(数据到达后由真实内容提供可访问名称);
- 键盘:骨架屏不可聚焦(not focusable),不会打断 Tab 导航序列;
- 颜色亮度:骨架背景色取"可见所需的最低亮度",即在良好环境光、良好屏幕且无视觉障碍的条件下刚好可辨识,避免在正文上制造高对比度的视觉干扰。
补充一点来自源码的无障碍事实:如前所述,transitions.disable(减少动态效果)场景下 pulse/wave 动画会自动禁用,进一步照顾前庭敏感用户。
API 速查
结合 Skeleton.d.ts 与 skeletonClasses.ts 的类型定义,组件的公开 API 如下:
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
animation |
'pulse' | 'wave' | false |
'pulse' |
动画模式;false 完全禁用动画 |
variant |
'text' | 'rectangular' | 'rounded' | 'circular' |
'text' |
形状变体 |
width |
number | string |
- | 宽度;内联样式写入,覆盖 children 推断 |
height |
number | string |
- | 高度;text 变体下默认基准为 1.2em |
children |
React.ReactNode |
- | 传入真实组件以推断宽高(子元素被隐藏渲染) |
component |
React.ElementType |
'span' |
根节点组件(默认 span) |
classes |
Partial<SkeletonClasses> |
- | 覆盖 utility class 样式,key 包括 root、text、rectangular、rounded、circular、pulse、wave、withChildren、fitContent、heightAuto |
sx |
SxProps<Theme> |
- | 系统 prop,可用于改 bgcolor 等 |
组件实现细节补充:Skeleton 是 forwardRef 组件(Skeleton.js 第 236 行),支持 ref 转发;默认 props 可通过 DefaultPropsProvider(useDefaultProps({ name: 'MuiSkeleton' }))在全局注入,方便应用层面统一设置默认变体或动画。
小结与延伸阅读
本文以 skeleton.md 文档为骨架,覆盖了 Usage、Variants、Animations、Inferring dimensions、Color 与 Accessibility 全部章节,并结合以下仓库文件展开实现细节:
- 核心实现:Skeleton.js(变体/动画关键帧、children 推断、reduced-motion);
- 类名定义:skeletonClasses.ts;
- 类型定义:Skeleton.d.ts;
- 官方演示:Variants.tsx、Animations.tsx、YouTube.tsx、Facebook.tsx、SkeletonTypography.tsx、SkeletonChildren.tsx、SkeletonColor.tsx;
- 测试:Skeleton.test.js。
实用要点回顾:文字占位保持 variant="text" 并跟随排版;固定尺寸用 width/height;不想重复尺寸就用 children 推断;深色背景用 sx={{ bgcolor: ... }};动画可关闭且自动响应 reduce-motion 偏好。
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