PHP 骨架屏实现方案
骨架屏(Skeleton Screen)是一种在页面内容加载完成前展示的占位图形,能提升用户体验,以下是几种在 PHP 项目中实现骨架屏的方案:

服务端渲染骨架屏
<?php
// 骨架屏组件类
class SkeletonScreen {
public static function render($type = 'card') {
$skeleton = '';
switch ($type) {
case 'card':
$skeleton = '
<div class="skeleton-card">
<div class="skeleton-image skeleton-animation"></div>
<div class="skeleton-text skeleton-animation"></div>
<div class="skeleton-text skeleton-animation short"></div>
</div>';
break;
case 'list':
$skeleton = '
<div class="skeleton-list">
<div class="skeleton-item">
<div class="skeleton-avatar skeleton-animation"></div>
<div class="skeleton-content">
<div class="skeleton-text skeleton-animation"></div>
<div class="skeleton-text skeleton-animation short"></div>
</div>
</div>
</div>';
break;
case 'table':
$skeleton = '
<div class="skeleton-table">
<div class="skeleton-row">
<div class="skeleton-cell skeleton-animation"></div>
<div class="skeleton-cell skeleton-animation"></div>
<div class="skeleton-cell skeleton-animation"></div>
</div>
<div class="skeleton-row">
<div class="skeleton-cell skeleton-animation"></div>
<div class="skeleton-cell skeleton-animation"></div>
<div class="skeleton-cell skeleton-animation"></div>
</div>
</div>';
break;
}
return $skeleton;
}
}
// 在页面中使用
$pageContent = getPageContent(); // 获取实际内容
$isLoading = empty($pageContent);
if ($isLoading) {
echo SkeletonScreen::render('card');
} else {
echo $pageContent;
}
?>
CSS 动画骨架屏
/* skeleton.css */
.skeleton-animation {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease infinite;
border-radius: 4px;
}
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.skeleton-card {
padding: 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.skeleton-image {
width: 100%;
height: 200px;
margin-bottom: 16px;
}
.skeleton-text {
width: 100%;
height: 16px;
margin-bottom: 8px;
}
.skeleton-text.short {
width: 60%;
}
.skeleton-list {
padding: 16px;
}
.skeleton-item {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.skeleton-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
margin-right: 12px;
}
.skeleton-content {
flex: 1;
}
.skeleton-table {
padding: 16px;
}
.skeleton-row {
display: flex;
margin-bottom: 8px;
}
.skeleton-cell {
flex: 1;
height: 20px;
margin-right: 8px;
}
PHP 中间件实现
<?php
// 骨架屏中间件
class SkeletonMiddleware {
public function handle($request, Closure $next) {
$response = $next($request);
// 检查是否需要显示骨架屏
if ($this->shouldShowSkeleton($request)) {
$content = $response->getContent();
// 生成骨架屏 HTML
$skeletonHtml = $this->generateSkeleton($request->getPath());
// 替换内容容器
$content = str_replace(
'<div id="content-container"></div>',
$skeletonHtml,
$content
);
$response->setContent($content);
}
return $response;
}
private function shouldShowSkeleton($request) {
// 判断条件:首次加载、API 请求中、缓存未命中等
return !$request->isAjax() && !$request->has('no_skeleton');
}
private function generateSkeleton($path) {
// 根据不同页面路径返回不同骨架屏
$skeletons = [
'/' => 'home',
'/products' => 'list',
'/users' => 'table',
];
$type = $skeletons[$path] ?? 'card';
return SkeletonScreen::render($type);
}
}
结合 JavaScript 动态切换
<?php
// PHP 生成骨架屏 + JS 切换实际内容
class HybridSkeleton {
public static function renderWithContent($content, $type = 'card') {
$skeleton = SkeletonScreen::render($type);
return <<<HTML
<div id="skeleton-container" data-type="{$type}">
{$skeleton}
</div>
<div id="content-container" style="display:none;">
{$content}
</div>
<script>
// 页面加载完成后,5秒后切换
setTimeout(function() {
document.getElementById('skeleton-container').style.display = 'none';
document.getElementById('content-container').style.display = 'block';
}, 5000);
// 或者等实际内容加载完成后切换
window.onload = function() {
document.getElementById('skeleton-container').style.display = 'none';
document.getElementById('content-container').style.display = 'block';
};
</script>
HTML;
}
}
使用缓存提高性能
<?php
class CachedSkeleton {
private $cacheDir = '/tmp/skeleton_cache/';
public function getCachedSkeleton($page, $type) {
$cacheFile = $this->cacheDir . md5($page . $type) . '.html';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 3600) {
return file_get_contents($cacheFile);
}
$skeleton = SkeletonScreen::render($type);
file_put_contents($cacheFile, $skeleton);
return $skeleton;
}
}
使用示例
<?php
// 在控制器中使用
class UserController {
public function index() {
$users = UserRepository::getAll();
if (empty($users)) {
// 显示骨架屏
return SkeletonScreen::render('table');
}
// 显示实际内容
return view('users.index', ['users' => $users]);
}
}
// 或者在模板中使用
// 在 Blade 或 PHP 模板中:
@if($isLoading)
{!! App\Helpers\SkeletonScreen::render('card') !!}
@else
@foreach($items as $item)
<div class="real-content">{{ $item->name }}</div>
@endforeach
@endif
最佳实践建议
- 选择合适的骨架屏类型:根据不同页面内容选择卡片、列表、表格等类型
- 优化加载时机:在首次加载或异步请求时显示骨架屏
- 缓存骨架屏 HTML:减少重复生成的开销
- 支持多种屏幕尺寸:确保骨架屏在移动端和桌面端都显示良好
- 考虑无障碍性:给骨架屏元素添加
aria-hidden="true"属性
这样可以在 PHP 项目中有效实现骨架屏,提升用户体验。