骨架屏是视觉占位与渐变过渡组合,用linear-gradient+background-position动画模拟扫描光效,需固定尺寸、脱离文档流、JS控制显隐,并适配SSR/CSR渲染时机。

用 animation + keyframes 模拟骨架屏闪烁效果
骨架屏本质不是“加载中动画”,而是视觉占位+渐变过渡的组合。CSS 动画只负责「模拟内容未就绪时的视觉节奏」,不参与数据加载逻辑。核心是用 linear-gradient 生成灰白条状背景,再通过 animation 移动 background-position 制造扫描光效。
- 推荐使用
to right方向的线性渐变,比径向更易控制扫描节奏 - 动画时长建议设为
1.5s ~ 2.5s,太短显急促,太长降低感知响应 - 必须加
background-size: 200% 100%,否则background-position位移无效
@keyframes loading-skeleton {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading-skeleton 2s ease-in-out infinite;
}
骨架元素需脱离文档流避免重排
如果直接给 <div> 加骨架样式,内容加载后替换会触发 layout shift(布局偏移),造成页面抖动。正确做法是让骨架层和真实内容共用同一容器,用 position: absolute 叠加,或用 visibility: hidden 控制显隐而非 display: none。
- 骨架容器设
position: relative,内部骨架元素设position: absolute; top: 0; left: 0; width: 100%; height: 100% - 真实内容加载完成时,用 JS 切换类名,把骨架元素
visibility: hidden,而不是移除 DOM - 避免对骨架元素设置
height: auto,必须固定高度或用 aspect-ratio 保形
不同组件需定制化骨架结构
按钮、头像、卡片的骨架形态差异大,不能靠一个 class 全局覆盖。比如头像要圆角+渐变遮罩,列表项需等高行高+多段灰条,而按钮需要宽度自适应+内边距留白。
- 头像骨架:
border-radius: 50%+background-image: radial-gradient(...) - 文字行骨架:用多个
<span class="skeleton-line">,每行设不同width模拟长短文本 - 避免用伪元素
::before做骨架——它无法继承父容器的宽高约束,响应式下容易错位
.avatar-skeleton {
border-radius: 50%;
background: radial-gradient(circle, #f5f5f5 20%, #e8e8e8 80%);
}
.skeleton-line {
display: block;
height: 16px;
margin: 8px 0;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading-skeleton 2s ease-in-out infinite;
}
SSR 或首屏直出时骨架屏容易失效
服务端渲染(SSR)或静态生成(SSG)页面首次加载时,骨架屏可能一闪而过甚至不出现,因为 HTML 已含真实内容,JS 还没执行完。此时需配合服务端逻辑,在数据未就绪时主动输出骨架 HTML 片段。
立即学习“前端免费学习笔记(深入)”;
- Next.js / Nuxt 等框架中,骨架应作为 fallback 组件写在
loading.tsx或loading.vue中,由框架自动接管 - 纯 CSR 项目可在组件挂载前用
useState(false)控制骨架显隐,但首次渲染仍需同步判断data === null - 关键点:骨架屏是否生效,取决于「HTML 输出时机」和「JS 渲染时机」的竞态关系,不能只靠 CSS 动画










