骨架屏是用灰色块模拟真实内容结构的加载占位技术,能提前占位、避免布局跳动,提升用户感知性能;而loading.gif仅提供局部动效,易破坏布局流且无法传递内容结构预期。

什么是骨架屏,为什么不用 loading.gif
骨架屏不是动效加载图标,而是用灰色块模拟真实内容的结构——比如头像位置放个圆角矩形,标题行用长条灰块,段落用三段不等长灰条。用户感知到的是“内容快来了”,而不是“系统在卡”。loading.gif 在首屏或长列表里会破坏布局流,而骨架屏能提前占位、避免重排,尤其对 SSR 或 hydration 前的 React/Vue 应用更友好。
CSS 实现骨架屏的关键技巧
核心是用 background-image 搭配 linear-gradient 造出“呼吸感”动画,同时确保骨架元素尺寸与真实 DOM 一致(否则 hydration 后会跳动):
- 所有骨架元素必须设置和真实内容完全相同的
width、height、margin、padding,推荐用 CSS 自定义属性统一控制 - 动画用
background-image: linear-gradient(...)+background-size+animation,不要用opacity淡入——它无法体现“内容正在浮现”的隐喻 - 避免给骨架容器设
display: none,应该用visibility: hidden或 class 切换,否则 Vue/React 的 diff 会误判节点销毁
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
Vue/React 中如何安全切换骨架屏与真实内容
关键不是“显示/隐藏”,而是“复用 DOM 节点”。如果骨架和真实内容结构不同,框架 hydration 时会丢弃骨架节点、重建真实节点,导致闪动甚至事件绑定丢失:
- Vue:用
v-if+v-else是错的;应始终渲染同一组元素,仅通过 class 控制视觉状态,例如:<div :class="{ skeleton: !loaded }">{{ data.title }}</div> - React:避免
{isLoading ? <skeleton></skeleton> : <content></content>};改用{data ? <content data="{data}"></content> : <skeleton></skeleton>},且Skeleton组件返回和Content完全一致的 JSX 结构(同标签、同 className、同嵌套层级) - 服务端渲染场景下,骨架屏只应在客户端生效:检查
typeof window !== 'undefined'再挂载动画,否则 Node 环境报错
哪些地方最容易翻车
骨架屏看似简单,但实际落地时最常栽在三个细节上:
立即学习“前端免费学习笔记(深入)”;
- 图片骨架没设
aspect-ratio或height,加载后高度塌陷,触发页面重排 - 文字行高未对齐,骨架灰块和真实文字 baseline 错位,看起来像“往上飘了一截”
- 响应式断点没同步处理骨架尺寸,比如移动端把标题缩成一行,但骨架仍按桌面端三行渲染
- 使用了
transform: scale()做骨架缩放动画——这会让子元素继承缩放,导致内部文字模糊或点击区域偏移
真正省心的做法,是把骨架组件和业务组件写在同一个文件里,用同一套变量控制尺寸和断点,而不是当成两个独立 UI 层去维护。



















