前端骨架屏实现详解
1. 引言:什么是骨架屏?
骨架屏是解决用户等待焦虑的神器。当页面加载时,先展示一个模拟真实内容布局的占位元素(骨架),告知用户'正在加载中',避免白屏带来的体验下降。
2. 学习目标
- 搞懂骨架屏的基本原理和机制;
- 掌握原生 JS 实现骨架屏的方法;
- 熟悉 Vue、React、Angular 等框架的最佳实践;
- 熟练运用移动端和小程序骨架屏实现;
- 会排查骨架屏常见坑点,避免踩雷。
3. 骨架屏原理详解
3.1 骨架屏基本流程图
graph TD
A[页面开始加载] --> B{数据是否加载完成?}
B -- 否 --> C[显示骨架屏]
C --> D[展示占位元素]
D --> B
B -- 是 --> E[隐藏骨架屏]
E --> F[渲染真实内容]
3.2 用户感知时间对比
使用骨架屏可显著降低用户感知的加载时间,提升满意度。
注意:骨架屏涉及用户体验、性能优化与视觉设计,不仅仅是画框。
4. 原生 JS 骨架屏实现思路与算法步骤
4.1 实现思路
- 在真实内容加载前,插入占位元素;
- 占位元素模拟真实内容的布局结构;
- 数据加载完成后,移除占位元素,显示真实内容。
4.2 算法步骤
步骤 1:创建骨架屏 HTML 结构
<div id="skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
</div>
</div>
<div id="content" style="display:none;">
<!-- 真实内容 -->
</div>
步骤 2:添加 CSS 样式
.skeleton-header {
width: 100%;
height: 60px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
.skeleton-line {
width: 100%;
height: 20px;
margin: 10px 0;
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; }
}
步骤 3:JavaScript 控制显示/隐藏
// 模拟数据加载
function loadData() {
return new Promise(resolve => {
setTimeout(() => {
resolve({ title: '真实标题', content: '真实内容内容内容...' });
}, 2000);
});
}
// 显示骨架屏
function showSkeleton() {
document.getElementById('skeleton').style.display = 'block';
document.getElementById('content').style.display = 'none';
}
// 隐藏骨架屏,显示真实内容
function hideSkeleton(data) {
document.getElementById('skeleton').style.display = 'none';
document.getElementById('content').style.display = 'block';
document.getElementById('content').innerHTML = `
<h1>${data.title}</h1>
<p></p>
`;
}
() {
();
data = ();
(data);
}
();
5. Vue 骨架屏最佳实践
5.1 Vue 组件实现
核心思想
- 通过
loading状态控制骨架屏和真实内容的显示切换; - 使用
v-if指令根据加载状态显示不同内容; - 用渐变动画模拟'加载中'的效果;
- 加载完成后更新数据,自动切换到真实内容。
代码详解
<template>
<div class="page-container">
<!-- 骨架屏 -->
<div v-if="loading" class="skeleton-wrapper">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-line" v-for="i in 5" :key="i"></div>
</div>
</div>
<!-- 真实内容 -->
<div v-else class="content-wrapper">
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'ArticlePage',
data() {
return {
loading: true,
article: {}
};
},
async mounted() {
// 模拟 API 调用
const response = await this.fetchArticle();
this.article = response;
this.loading = false;
},
methods: {
fetchArticle() {
return new Promise(resolve => {
setTimeout(() => {
resolve({ title: 'Vue 骨架屏实践', content: '这是 Vue 骨架屏的详细内容...' });
}, 2000);
});
}
}
};
</script>
<style scoped>
.skeleton-header {
width: 100%;
height: 60px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
margin-bottom: 20px;
}
.skeleton-line {
width: 100%;
height: 20px;
margin: 10px 0;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
关键点分析
v-if="loading":核心逻辑,控制显示状态。v-for:循环生成骨架行,模拟内容区域。scoped:样式作用域限制,避免影响其他组件。
优化建议
- 添加最小显示时间:防止网络极快时骨架屏一闪而过。
async mounted() { const startTime = Date.now(); const response = await this.fetchArticle(); const elapsed = Date.now() - startTime; if (elapsed < 1000) { await new Promise(resolve => setTimeout(resolve, 1000 - elapsed)); } this.article = response; this.loading = false; } - 错误处理:确保加载失败也能关闭骨架屏。
async mounted() { try { const response = await this.fetchArticle(); this.article = response; } catch (error) { console.error('加载失败:', error); } finally { this.loading = false; } }
6. React 骨架屏最佳实践
6.1 React 组件实现
import React, { useState, useEffect } from 'react';
import './Skeleton.css';
const SkeletonScreen = () => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
setTimeout(() => {
setData({ title: 'React 骨架屏实践', content: '这是 React 骨架屏的详细内容...' });
setLoading(false);
}, 2000);
};
fetchData();
}, []);
if (loading) {
return (
<div className="skeleton-wrapper">
<div className="skeleton-header"></div>
<div className="skeleton-content">
{[...Array(5)].map((_, index) => (
<div key={index} className="skeleton-line">
))}
);
}
(
);
};
;
6.2 CSS 样式文件
/* Skeleton.css */
.skeleton-wrapper { padding: 20px; }
.skeleton-header {
width: 100%;
height: 60px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
margin-bottom: 20px;
border-radius: 8px;
}
.skeleton-line {
width: 100%;
height: 20px;
margin: 10px 0;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
@keyframes loading {
0% { background-position: ; }
{ : - ; }
}
7. jQuery 骨架屏实现
<!DOCTYPE html>
<html>
<head>
<title>jQuery 骨架屏</title>
<style>
.skeleton-header {
width: 100%;
height: 60px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
margin-bottom: 20px;
}
.skeleton-line {
width: 100%;
height: 20px;
margin: 10px 0;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
{ : ; }
{ : - ; }
}
8. Angular 骨架屏最佳实践
// skeleton.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-skeleton',
template: `
<div *ngIf="loading">
<div></div>
<div>
<div *ngFor="let i of [1,2,3,4,5]"></div>
</div>
</div>
<div *ngIf="!loading">
<h1>{{ data.title }}</h1>
<p>{{ data.content }}</p>
</div>
`,
styles: [`
.skeleton-header { width: 100%; height: 60px; background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: loading 1.5s infinite; margin-bottom: 20px; border-radius: 8px; }
.skeleton-line { width: 100%; height: 20px; margin: 10px 0; background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: loading 1.5s infinite; border-radius: 4px; }
@keyframes loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
`]
})
export class SkeletonComponent implements OnInit {
loading = true;
data: any = {};
ngOnInit() {
this.loadData();
}
loadData() {
setTimeout(() => {
this.data = { title: 'Angular 骨架屏实践', content: '这是 Angular 骨架屏的详细内容...' };
this.loading = false;
}, 2000);
}
}
9. 移动端骨架屏最佳实践
9.1 移动端适配要点
- 响应式设计:适配不同屏幕尺寸;
- 触摸友好:避免误触;
- 性能优化:减少 DOM 操作。
9.2 移动端 CSS 优化
/* 移动端骨架屏样式 */
.skeleton-mobile { padding: 15px; }
.skeleton-mobile .skeleton-header { height: 50px; border-radius: 6px; }
.skeleton-mobile .skeleton-line { height: 16px; margin: 8px 0; border-radius: 3px; }
/* 媒体查询适配 */
@media(max-width: 768px) {
.skeleton-mobile .skeleton-header { height: 45px; }
.skeleton-mobile .skeleton-line { height: 14px; margin: 6px 0; }
}
10. 小程序骨架屏最佳实践
10.1 微信小程序实现
<!-- skeleton.wxml -->
<view class="skeleton-container" wx:if="{{loading}}">
<view class="skeleton-header"></view>
<view class="skeleton-content">
<view class="skeleton-line" wx:for="{{lines}}" wx:key="index"></view>
</view>
</view>
<view class="content-container" wx:else>
<text class="title">{{article.title}}</text>
<text class="content">{{article.content}}</text>
</view>
/* skeleton.wxss */
.skeleton-header {
width: 100%;
height: 60rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
margin-bottom: 20rpx;
border-radius: 8rpx;
}
.skeleton-line {
width: 100%;
height: 30rpx;
margin: 15rpx 0;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4rpx;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { : - ; }
}
// skeleton.js
Page({
data: {
loading: true,
lines: [1, 2, 3, 4, 5],
article: {}
},
onLoad() {
this.loadArticle();
},
loadArticle() {
setTimeout(() => {
this.setData({
loading: false,
article: { title: '小程序骨架屏实践', content: '这是小程序骨架屏的详细内容...' }
});
}, 2000);
}
});
11. 骨架屏性能对比与优化建议
11.1 性能对比表
| 实现方式 | 加载感知时间 | 用户满意度 | 实现复杂度 |
|---|---|---|---|
| 无骨架屏 | 3.2s | 60% | 简单 |
| 骨架屏 | 2.1s | 85% | 中等 |
| 预加载 | 1.5s | 95% | 复杂 |
11.2 优化建议
- 合理使用:只在必要时使用骨架屏;
- 样式简洁:避免过于复杂的动画效果;
- 内容匹配:骨架屏结构要与真实内容一致;
- 性能监控:监控骨架屏对性能的影响。
12. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 骨架屏闪烁 | 显示时间过短 | 增加最小显示时间 |
| 布局错位 | 样式不匹配 | 精确匹配真实内容布局 |
| 性能下降 | 动画过多 | 简化动画效果 |
| 兼容性问题 | CSS 不支持 | 使用兼容性更好的方案 |
13. 总结
| 项目 | 内容 |
|---|---|
| 核心原理 | 占位元素 + 动画效果 |
| 实现方式 | 原生 JS、Vue、React、Angular 等 |
| 关键技术 | CSS 动画、条件渲染 |
| 移动端适配 | 响应式设计、触摸优化 |
| 小程序支持 | 微信、支付宝等平台 |
| 性能优化 | 减少 DOM 操作、简化动画 |
| 用户体验 | 降低等待焦虑、提升感知速度 |
14. 结语
骨架屏只是优化用户体验的第一步,后面还有懒加载、预加载、缓存策略等等一堆东西等着你去探索。动手实践,让页面加载更丝滑!


