骨架屏
骨架屏:在真实内容加载前显示内容结构占位,提升用户感知速度。
#type / concept
#status / growing
#tech / dev / frontend
[!info] related notes
- 所属 MOC: 前端性能优化 MOC
- 前置概念: lazy-loading
- 并列概念: optimistic-update
骨架屏
一句话定义
骨架屏是在页面加载时显示与真实布局匹配的灰色占位块,通过渐进展示内容结构来提升用户感知性能的优化技术。
核心机制 / 工作原理
与 Loading Spinner 对比
| 维度 | 骨架屏 | Loading Spinner |
|---|---|---|
| 用户感知 | ”内容正在来" | "不知道在等什么” |
| 布局信息 | 提前展示页面结构 | 完全无信息 |
| 跳变风险 | 低(尺寸已预留) | 高(内容突然出现) |
| 适用场景 | 内容结构可预测 | 结构未知或简单等待 |
实现方式
CSS 渐变动画(shimmer)
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
React 骨架屏组件
function SkeletonCard() {
return (
<div className="card">
<div className="skeleton" style={{ width: '100%', height: 180 }} />
<div className="skeleton" style={{ width: '60%', height: 20, marginTop: 12 }} />
<div className="skeleton" style={{ width: '80%', height: 14, marginTop: 8 }} />
</div>
);
}
条件渲染
function ArticlePage() {
const { data, loading } = useFetch('/api/article');
if (loading) return <ArticleSkeleton />;
return <ArticleContent data={data} />;
}
设计原则
- 匹配真实布局:骨架块的尺寸、间距应与实际内容一致
- 使用 shimmer 动画:纯色块太死板,微动画暗示”正在加载”
- 避免纯白/纯黑:用浅灰色系,有”占位”的视觉暗示
- 预留正确尺寸:图片、文字区域的高度要准确,防止内容加载后跳变
- 渐进消失:内容到达后逐区域替换,而非整体闪烁切换
应用场景
- 列表页(Feed、搜索结果)
- 详情页(文章、商品详情)
- 卡片流(社交动态、瀑布流)
- 表单页(复杂表单加载配置后填充)
- 首屏加载(SPA 首次进入)
最小例子
一个最简单的列表骨架屏:
<!-- 骨架状态 -->
<div class="list-item skeleton">
<div class="skeleton-avatar"></div>
<div class="skeleton-text"></div>
</div>
<!-- 真实内容替换 -->
<div class="list-item">
<img src="avatar.jpg" />
<p>真实文本内容</p>
</div>
CSS 中 .skeleton-avatar 和 .skeleton-text 使用 shimmer 动画样式,尺寸与真实元素一致。
边界与常见误解
- 骨架屏不是性能优化:不减少加载时间,只改善感知;真正优化还需懒加载、缓存等手段
- 骨架屏 ≠ 占位图(placeholder):placeholder 是低质量内容预览(如模糊缩略图),骨架屏是结构占位
- CLS 风险:如果骨架尺寸与真实内容不匹配,加载完成时会导致布局偏移,反而恶化 CLS
- 过度设计:简单场景用 Loading Spinner 即可,不需要每个页面都做骨架屏
- SEO 影响:骨架屏是客户端渲染的,搜索引擎爬虫看到的可能是空壳;需配合 SSR/SSG