feat: 全新设计的现代化页面加载系统

设计亮点:
- SVG 圆环进度指示器,实时显示加载进度
- 智能进度算法:自动递增 + 缓动效果
- 中心旋转图标 + 脉冲动画
- 延迟显示骨架屏(避免快速加载时闪烁)
- 最小显示时间控制(400ms)防止闪烁

加载逻辑优化:
- 智能进度管理:0-90% 自动递增,完成时跳转 100%
- 缓动函数:越接近完成速度越慢,更自然
- 定时器管理:防止内存泄漏和状态冲突
- 骨架屏延迟 150ms 显示,快速加载不显示

视觉设计:
- 渐变背景 + 毛玻璃效果
- 弹性入场动画(scale + translateY)
- 流畅的光影扫过效果
- 完整的响应式适配
- 支持无障碍访问(prefers-reduced-motion)
This commit is contained in:
2026-01-27 16:52:11 +08:00
parent 73103ea853
commit 1eb5d85eaf
2 changed files with 432 additions and 301 deletions

View File

@@ -3200,166 +3200,268 @@ var pjaxContainers = pjaxContainerSelectors.filter(function(selector) {
}); });
// ========================================================================== // ==========================================================================
// 页面加载动画管理器 // 现代化页面加载系统
// ========================================================================== // ==========================================================================
/** /**
* 加载动画管理器 * 页面加载管理器 - 提供智能加载动画和进度追踪
* 负责创建、显示和隐藏页面加载动画
*/ */
const LoadingOverlay = (function() { const PageLoader = (function() {
const OVERLAY_ID = 'article-loading-overlay'; // 配置常量
const ANIMATION_DURATION = 300; const CONFIG = {
const CLASS_VISIBLE = 'is-visible'; OVERLAY_ID: 'page-loader',
const CLASS_HIDING = 'is-hiding'; MIN_DISPLAY_TIME: 400, // 最小显示时间(避免闪烁)
FADE_DURATION: 350, // 淡出动画时长
PROGRESS_STEP: 0.1, // 进度条步进
PROGRESS_INTERVAL: 200, // 进度更新间隔
SKELETON_DELAY: 150 // 骨架屏延迟显示
};
let overlayElement = null; // 状态管理
let hideTimer = null; let state = {
element: null,
isVisible: false,
startTime: 0,
progress: 0,
progressTimer: null,
hideTimer: null,
skeletonTimer: null
};
/** /**
* 创建骨架屏 HTML 结构 * 创建加载动画 HTML
* @returns {string} HTML 字符串
*/ */
function createSkeletonHTML() { function createHTML() {
return ` return `
<div class="loading-overlay-content"> <div class="page-loader-backdrop"></div>
<div class="loading-card"> <div class="page-loader-content">
<div class="loading-thumb"> <!-- 进度环 -->
<div class="loading-shimmer"></div> <div class="loader-ring-container">
</div> <svg class="loader-ring" viewBox="0 0 100 100">
<div class="loading-body"> <circle class="loader-ring-bg" cx="50" cy="50" r="45"></circle>
<div class="loading-meta"> <circle class="loader-ring-progress" cx="50" cy="50" r="45"></circle>
<div class="loading-avatar"></div> </svg>
<div class="loading-meta-text"> <div class="loader-icon">
<div class="loading-meta-line" style="width: 120px"></div> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<div class="loading-meta-line" style="width: 80px"></div> <path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83"/>
</svg>
</div> </div>
</div> </div>
<div class="loading-title"></div>
<div class="loading-text"> <!-- 加载文字 -->
<div class="loading-line" style="width: 95%"></div> <div class="loader-text">
<div class="loading-line" style="width: 88%"></div> <div class="loader-title">加载中</div>
<div class="loading-line" style="width: 92%"></div> <div class="loader-subtitle">正在为您准备内容</div>
<div class="loading-line" style="width: 78%"></div>
</div> </div>
<div class="loading-tags">
<div class="loading-tag"></div> <!-- 骨架屏(延迟显示) -->
<div class="loading-tag"></div> <div class="loader-skeleton">
<div class="loading-tag"></div> <div class="skeleton-card">
<div class="skeleton-image"></div>
<div class="skeleton-content">
<div class="skeleton-title"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="loading-spinner-wrapper">
<div class="loading-spinner">
<div class="loading-spinner-ring"></div>
</div>
<div class="loading-text-hint">正在加载精彩内容</div>
<div class="loading-dots">
<span class="loading-dot"></span>
<span class="loading-dot"></span>
<span class="loading-dot"></span>
</div>
</div>
</div> </div>
`; `;
} }
/** /**
* 创建加载动画元素 * 创建加载元素
* @returns {HTMLElement} 创建的元素
*/ */
function createElement() { function createElement() {
const el = document.createElement('div'); const el = document.createElement('div');
el.id = OVERLAY_ID; el.id = CONFIG.OVERLAY_ID;
el.className = 'loading-overlay'; el.className = 'page-loader';
el.innerHTML = createSkeletonHTML(); el.innerHTML = createHTML();
return el; return el;
} }
/** /**
* 获取或创建加载动画元素 * 获取或创建元素
* @returns {HTMLElement} 加载动画元素
*/ */
function getElement() { function getElement() {
if (!overlayElement) { if (!state.element) {
overlayElement = document.getElementById(OVERLAY_ID); state.element = document.getElementById(CONFIG.OVERLAY_ID);
if (!state.element) {
state.element = createElement();
document.body.appendChild(state.element);
} }
if (!overlayElement) {
overlayElement = createElement();
document.body.appendChild(overlayElement);
} }
return overlayElement; return state.element;
} }
/** /**
* 显示加载动画 * 更新进度环
*/
function updateProgress(progress) {
const el = state.element;
if (!el) return;
const circle = el.querySelector('.loader-ring-progress');
if (circle) {
const circumference = 2 * Math.PI * 45;
const offset = circumference - (progress / 100) * circumference;
circle.style.strokeDashoffset = offset;
}
state.progress = progress;
}
/**
* 自动递增进度
*/
function startProgressAnimation() {
stopProgressAnimation();
state.progress = 0;
updateProgress(0);
state.progressTimer = setInterval(function() {
if (state.progress < 90) {
// 使用缓动函数,越接近 90% 越慢
const increment = CONFIG.PROGRESS_STEP * (1 - state.progress / 100);
state.progress = Math.min(90, state.progress + increment * 10);
updateProgress(state.progress);
}
}, CONFIG.PROGRESS_INTERVAL);
}
/**
* 停止进度动画
*/
function stopProgressAnimation() {
if (state.progressTimer) {
clearInterval(state.progressTimer);
state.progressTimer = null;
}
}
/**
* 完成进度到 100%
*/
function completeProgress() {
stopProgressAnimation();
updateProgress(100);
}
/**
* 显示加载器
*/ */
function show() { function show() {
// 清除可能存在的隐藏定时器 // 清理之前的定时器
if (hideTimer) { if (state.hideTimer) {
clearTimeout(hideTimer); clearTimeout(state.hideTimer);
hideTimer = null; state.hideTimer = null;
}
if (state.skeletonTimer) {
clearTimeout(state.skeletonTimer);
state.skeletonTimer = null;
} }
const el = getElement(); const el = getElement();
el.classList.remove(CLASS_HIDING); state.startTime = Date.now();
state.isVisible = true;
// 强制重排以确保动画生效 // 移除隐藏类,添加显示类
void el.offsetWidth; el.classList.remove('is-hiding');
void el.offsetWidth; // 强制重排
el.classList.add('is-visible');
el.classList.add(CLASS_VISIBLE); // 启动进度动画
startProgressAnimation();
// 延迟显示骨架屏(避免快速加载时闪烁)
state.skeletonTimer = setTimeout(function() {
if (state.isVisible && el) {
el.classList.add('show-skeleton');
}
}, CONFIG.SKELETON_DELAY);
} }
/** /**
* 隐藏加载动画 * 隐藏加载
*/ */
function hide() { function hide() {
const el = document.getElementById(OVERLAY_ID); if (!state.isVisible) return;
const el = state.element;
if (!el) return; if (!el) return;
el.classList.add(CLASS_HIDING); // 完成进度
completeProgress();
// 动画结束后清理状态 // 计算已显示时间
hideTimer = setTimeout(function() { const elapsedTime = Date.now() - state.startTime;
el.classList.remove(CLASS_VISIBLE, CLASS_HIDING); const remainingTime = Math.max(0, CONFIG.MIN_DISPLAY_TIME - elapsedTime);
hideTimer = null;
}, ANIMATION_DURATION); // 确保最小显示时间后再隐藏
state.hideTimer = setTimeout(function() {
el.classList.add('is-hiding');
el.classList.remove('show-skeleton');
// 动画结束后清理
setTimeout(function() {
el.classList.remove('is-visible', 'is-hiding');
state.isVisible = false;
stopProgressAnimation();
}, CONFIG.FADE_DURATION);
}, remainingTime);
} }
/** /**
* 销毁加载动画元素 * 设置进度(手动控制)
*/
function setProgress(progress) {
progress = Math.max(0, Math.min(100, progress));
stopProgressAnimation();
updateProgress(progress);
}
/**
* 销毁加载器
*/ */
function destroy() { function destroy() {
if (hideTimer) { stopProgressAnimation();
clearTimeout(hideTimer);
hideTimer = null; if (state.hideTimer) {
clearTimeout(state.hideTimer);
state.hideTimer = null;
} }
if (overlayElement && overlayElement.parentNode) { if (state.skeletonTimer) {
overlayElement.parentNode.removeChild(overlayElement); clearTimeout(state.skeletonTimer);
overlayElement = null; state.skeletonTimer = null;
} }
if (state.element && state.element.parentNode) {
state.element.parentNode.removeChild(state.element);
}
state.element = null;
state.isVisible = false;
} }
// 公开 API // 公开 API
return { return {
show: show, show: show,
hide: hide, hide: hide,
setProgress: setProgress,
destroy: destroy destroy: destroy
}; };
})(); })();
/** /**
* 显示页面加载动画(向后兼容 * 向后兼容的函数
*/ */
function showLoadingOverlay() { function showLoadingOverlay() {
LoadingOverlay.show(); PageLoader.show();
} }
/**
* 隐藏页面加载动画(向后兼容)
*/
function hideLoadingOverlay() { function hideLoadingOverlay() {
LoadingOverlay.hide(); PageLoader.hide();
} }
function startPageTransition() { function startPageTransition() {
document.documentElement.classList.add('page-transition-enter'); document.documentElement.classList.add('page-transition-enter');

449
style.css
View File

@@ -17048,10 +17048,10 @@ article img.loaded, .post-thumbnail img.loaded, article img:not([loading="lazy"]
@keyframes modernSkeletonPulse { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } @keyframes modernSkeletonPulse { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
.skeleton { background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%); background-size: 200% 100%; animation: modernSkeletonPulse 1.5s ease-in-out infinite; border-radius: var(--card-radius); } .skeleton { background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%); background-size: 200% 100%; animation: modernSkeletonPulse 1.5s ease-in-out infinite; border-radius: var(--card-radius); }
/* ========================================================================== /* ==========================================================================
页面加载动画 现代化页面加载系统
========================================================================== */ ========================================================================== */
/* ---------- 基础旋转器 ---------- */ /* ---------- 基础旋转器(保留用于其他地方) ---------- */
@keyframes modernSpinnerRotate { @keyframes modernSpinnerRotate {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
@@ -17078,90 +17078,220 @@ article img.loaded, .post-thumbnail img.loaded, article img:not([loading="lazy"]
box-shadow: 0 0 10px rgba(var(--themecolor-rgbstr), 0.5); box-shadow: 0 0 10px rgba(var(--themecolor-rgbstr), 0.5);
} }
/* ---------- 加载遮罩层 ---------- */ /* ---------- 页面加载器容器 ---------- */
.loading-overlay { .page-loader {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9998; z-index: 9998;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: rgba(255, 255, 255, 0.3);
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
transition: opacity var(--animation-normal) var(--ease-standard), visibility 0s linear var(--animation-normal); transition: opacity 0.35s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0.35s;
-webkit-backdrop-filter: blur(var(--card-blur, 20px)) saturate(var(--card-saturate, 180%));
backdrop-filter: blur(var(--card-blur, 20px)) saturate(var(--card-saturate, 180%));
} }
html.darkmode .loading-overlay { .page-loader.is-visible {
background: rgba(0, 0, 0, 0.5);
}
.loading-overlay.is-visible {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
pointer-events: auto; pointer-events: auto;
transition: opacity var(--animation-normal) var(--ease-standard), visibility 0s; transition: opacity 0.35s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s;
} }
.loading-overlay.is-hiding { .page-loader.is-hiding {
opacity: 0; opacity: 0;
visibility: visible; transition: opacity 0.35s cubic-bezier(0.4, 0, 0.6, 1);
pointer-events: none; }
transition: opacity var(--animation-normal) var(--ease-standard);
/* ---------- 背景遮罩 ---------- */
.page-loader-backdrop {
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(var(--themecolor-rgbstr), 0.03) 0%, rgba(var(--themecolor-rgbstr), 0.08) 100%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
backdrop-filter: blur(24px) saturate(180%);
}
html.darkmode .page-loader-backdrop {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.6) 100%);
} }
/* ---------- 内容容器 ---------- */ /* ---------- 内容容器 ---------- */
.loading-overlay-content { .page-loader-content {
width: min(720px, 90vw); position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
transform: translateY(20px) scale(0.95);
opacity: 0; opacity: 0;
transform: translate3d(0, 12px, 0) scale(0.98); transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.4s ease;
transition: opacity var(--animation-normal) var(--ease-standard), transform var(--animation-normal) var(--ease-emphasized-decelerate);
} }
.loading-overlay.is-visible .loading-overlay-content { .page-loader.is-visible .page-loader-content {
transform: translateY(0) scale(1);
opacity: 1; opacity: 1;
transform: translate3d(0, 0, 0) scale(1); transition-delay: 0.1s;
} }
/* ---------- 卡片容器 ---------- */ /* ---------- 进度环容器 ---------- */
.loading-card { .loader-ring-container {
position: relative;
width: 120px;
height: 120px;
}
/* ---------- SVG 进度环 ---------- */
.loader-ring {
width: 100%;
height: 100%;
transform: rotate(-90deg);
filter: drop-shadow(0 4px 12px rgba(var(--themecolor-rgbstr), 0.2));
}
.loader-ring-bg {
fill: none;
stroke: var(--color-border);
stroke-width: 3;
opacity: 0.2;
}
.loader-ring-progress {
fill: none;
stroke: url(#loaderGradient);
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: 283;
stroke-dashoffset: 283;
transition: stroke-dashoffset 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 渐变定义(通过 JS 动态添加或使用内联 SVG */
.loader-ring::before {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: conic-gradient(from 0deg, var(--themecolor), transparent);
opacity: 0.1;
animation: ringGlow 2s ease-in-out infinite;
}
@keyframes ringGlow {
0%, 100% {
opacity: 0.1;
}
50% {
opacity: 0.3;
}
}
/* ---------- 中心图标 ---------- */
.loader-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 48px;
height: 48px;
color: var(--themecolor);
animation: iconPulse 2s ease-in-out infinite;
}
.loader-icon svg {
width: 100%;
height: 100%;
animation: iconRotate 3s linear infinite;
}
@keyframes iconPulse {
0%, 100% {
opacity: 0.6;
transform: translate(-50%, -50%) scale(1);
}
50% {
opacity: 1;
transform: translate(-50%, -50%) scale(1.1);
}
}
@keyframes iconRotate {
to {
transform: rotate(360deg);
}
}
/* ---------- 加载文字 ---------- */
.loader-text {
text-align: center;
opacity: 0;
transform: translateY(10px);
animation: textFadeIn 0.5s ease forwards;
animation-delay: 0.3s;
}
@keyframes textFadeIn {
to {
opacity: 1;
transform: translateY(0);
}
}
.loader-title {
font-size: 20px;
font-weight: 600;
color: var(--color-font);
margin-bottom: 8px;
letter-spacing: 0.5px;
}
.loader-subtitle {
font-size: 14px;
color: var(--color-font-sub);
font-weight: 400;
}
/* ---------- 骨架屏(延迟显示) ---------- */
.loader-skeleton {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
pointer-events: none;
}
.page-loader.show-skeleton .loader-skeleton {
opacity: 1;
transform: translateY(0);
}
/* ---------- 骨架卡片 ---------- */
.skeleton-card {
width: min(480px, 85vw);
background: var(--color-foreground); background: var(--color-foreground);
border-radius: var(--card-radius); border-radius: 16px;
overflow: hidden; overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
margin-bottom: 24px;
} }
html.darkmode .loading-card { html.darkmode .skeleton-card {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
} }
/* ---------- 缩略图骨架 ---------- */ /* ---------- 骨架图片 ---------- */
.loading-thumb { .skeleton-image {
position: relative;
width: 100%; width: 100%;
height: 240px; height: 200px;
overflow: hidden; background: linear-gradient(90deg, var(--color-border-on-foreground) 0%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 100%);
background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%);
background-size: 200% 100%; background-size: 200% 100%;
animation: skeletonPulse 1.8s ease-in-out infinite; animation: skeletonShimmer 1.5s ease-in-out infinite;
position: relative;
overflow: hidden;
} }
.loading-shimmer { .skeleton-image::after {
content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: -100%; left: -100%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
animation: shimmerMove 2s infinite; animation: shimmerSlide 2s infinite;
} }
html.darkmode .loading-shimmer { html.darkmode .skeleton-image::after {
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.05), transparent);
} }
@keyframes shimmerMove { @keyframes shimmerSlide {
to { to {
left: 100%; left: 100%;
} }
} }
@keyframes skeletonPulse { @keyframes skeletonShimmer {
0%, 100% { 0%, 100% {
background-position: 0% 0%; background-position: 0% 0%;
} }
@@ -17170,193 +17300,92 @@ html.darkmode .loading-shimmer {
} }
} }
/* ---------- 卡片主体 ---------- */ /* ---------- 骨架内容 ---------- */
.loading-body { .skeleton-content {
padding: 24px;
}
/* ---------- 元信息区域 ---------- */
.loading-meta {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.loading-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
flex-shrink: 0;
background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%);
background-size: 200% 100%;
animation: skeletonPulse 1.8s ease-in-out infinite;
}
.loading-meta-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.loading-meta-line {
height: 12px;
border-radius: 6px;
background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%);
background-size: 200% 100%;
animation: skeletonPulse 1.8s ease-in-out infinite;
}
/* ---------- 标题骨架 ---------- */
.loading-title {
height: 28px;
margin-bottom: 16px;
border-radius: 14px;
width: 85%;
background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%);
background-size: 200% 100%;
animation: skeletonPulse 1.8s ease-in-out infinite;
}
/* ---------- 文本行骨架 ---------- */
.loading-text {
margin-bottom: 16px;
}
.loading-line {
height: 14px;
margin: 8px 0;
border-radius: 7px;
background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%);
background-size: 200% 100%;
animation: skeletonPulse 1.8s ease-in-out infinite;
}
/* ---------- 标签骨架 ---------- */
.loading-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.loading-tag {
width: 60px;
height: 24px;
border-radius: 12px;
background: linear-gradient(90deg, var(--color-border-on-foreground) 25%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 75%);
background-size: 200% 100%;
animation: skeletonPulse 1.8s ease-in-out infinite;
}
/* ---------- 加载旋转器 ---------- */
.loading-spinner-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 20px; padding: 20px;
} }
.loading-spinner { .skeleton-title {
position: relative; height: 24px;
width: 56px; width: 70%;
height: 56px; border-radius: 12px;
background: linear-gradient(90deg, var(--color-border-on-foreground) 0%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 100%);
background-size: 200% 100%;
animation: skeletonShimmer 1.5s ease-in-out infinite;
margin-bottom: 16px;
} }
.loading-spinner::before { .skeleton-line {
content: ''; height: 14px;
position: absolute; border-radius: 7px;
inset: 0; background: linear-gradient(90deg, var(--color-border-on-foreground) 0%, var(--color-border-on-foreground-deeper) 50%, var(--color-border-on-foreground) 100%);
border-radius: 50%; background-size: 200% 100%;
border: 3px solid var(--color-border); animation: skeletonShimmer 1.5s ease-in-out infinite;
opacity: 0.2; margin-bottom: 10px;
} }
.loading-spinner-ring { .skeleton-line.short {
position: absolute; width: 60%;
inset: 0;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: var(--themecolor);
border-right-color: var(--themecolor);
animation: spinnerRotate 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
}
@keyframes spinnerRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* ---------- 加载文字 ---------- */
.loading-text-hint {
color: var(--color-font);
font-size: 15px;
font-weight: 500;
letter-spacing: 0.3px;
}
/* ---------- 加载点动画 ---------- */
.loading-dots {
display: flex;
gap: 6px;
}
.loading-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--themecolor);
animation: dotBounce 1.4s infinite ease-in-out both;
}
.loading-dot:nth-child(1) {
animation-delay: -0.32s;
}
.loading-dot:nth-child(2) {
animation-delay: -0.16s;
}
@keyframes dotBounce {
0%, 80%, 100% {
transform: scale(0.6);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
} }
/* ---------- 响应式适配 ---------- */ /* ---------- 响应式适配 ---------- */
@media (max-width: 768px) { @media (max-width: 768px) {
.loading-thumb { .loader-ring-container {
height: 180px; width: 100px;
height: 100px;
} }
.loading-body { .loader-icon {
padding: 20px; width: 40px;
height: 40px;
} }
.loading-title { .loader-title {
height: 24px; font-size: 18px;
width: 90%;
} }
.loading-spinner { .loader-subtitle {
width: 48px; font-size: 13px;
height: 48px;
} }
.loading-text-hint { .skeleton-image {
font-size: 14px; height: 160px;
}
.skeleton-content {
padding: 16px;
} }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.loading-overlay-content { .page-loader-content {
width: 95vw; gap: 24px;
} }
.loading-thumb { .loader-ring-container {
height: 160px; width: 90px;
height: 90px;
} }
.loading-body { .loader-icon {
padding: 16px;
}
.loading-meta {
gap: 10px;
}
.loading-avatar {
width: 36px; width: 36px;
height: 36px; height: 36px;
} }
.skeleton-card {
width: 92vw;
}
.skeleton-image {
height: 140px;
}
}
/* ---------- 减少动画(无障碍) ---------- */
@media (prefers-reduced-motion: reduce) {
.page-loader,
.page-loader-content,
.loader-ring-progress,
.loader-icon,
.loader-text,
.loader-skeleton,
.skeleton-image,
.skeleton-title,
.skeleton-line {
animation: none !important;
transition: none !important;
}
.page-loader.is-visible .page-loader-content {
transform: none;
opacity: 1;
}
} }
/* 页面过渡内容容器 */ /* 页面过渡内容容器 */