From 73103ea85321442e9c4a3fbd83fe17efebf11e4d Mon Sep 17 00:00:00 2001 From: nanhaoluo <3075912108@qq.com> Date: Tue, 27 Jan 2026 16:46:13 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=8A=A8=E7=94=BB=E4=BB=A3=E7=A0=81=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JavaScript 重构: - 创建 LoadingOverlay 模块化管理器(IIFE 模式) - 封装元素创建、显示、隐藏和销毁逻辑 - 添加定时器管理,防止内存泄漏 - 提供向后兼容的函数接口 - 使用常量管理配置项(ID、类名、动画时长) - 改进代码注释和 JSDoc 文档 CSS 重构: - 统一类名前缀为 loading-*,语义更清晰 - 移除 ID 选择器依赖,改用类选择器 - 分离关注点:基础旋转器、进度条、遮罩层、骨架屏 - 添加结构化注释,便于定位和修改 - 优化选择器层级,提升性能 - 独立动画关键帧定义 代码改进: - 减少重复代码,提高复用性 - 更好的错误处理和边界情况处理 - 支持多次调用不会重复创建元素 - 清晰的模块边界和职责划分 --- argontheme.js | 190 +++++++++++++++++++++++++++++++++++++------------- style.css | 185 ++++++++++++++++++++++++++++++------------------ 2 files changed, 260 insertions(+), 115 deletions(-) diff --git a/argontheme.js b/argontheme.js index fec56d4..604980b 100644 --- a/argontheme.js +++ b/argontheme.js @@ -3199,73 +3199,167 @@ var pjaxContainers = pjaxContainerSelectors.filter(function(selector) { return document.querySelector(selector); }); +// ========================================================================== +// 页面加载动画管理器 +// ========================================================================== + /** - * 显示页面加载动画遮罩 + * 加载动画管理器 + * 负责创建、显示和隐藏页面加载动画 */ -function showLoadingOverlay() { - let el = document.getElementById('article-loading-overlay'); - if (!el) { - // 动态创建加载动画元素 - el = document.createElement('div'); - el.id = 'article-loading-overlay'; - el.innerHTML = ` -
-
-
-
+const LoadingOverlay = (function() { + const OVERLAY_ID = 'article-loading-overlay'; + const ANIMATION_DURATION = 300; + const CLASS_VISIBLE = 'is-visible'; + const CLASS_HIDING = 'is-hiding'; + + let overlayElement = null; + let hideTimer = null; + + /** + * 创建骨架屏 HTML 结构 + * @returns {string} HTML 字符串 + */ + function createSkeletonHTML() { + return ` +
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
正在加载精彩内容
-
- - - +
正在加载精彩内容
+
+ + +
`; - document.body.appendChild(el); } - el.classList.remove('is-hiding'); - el.classList.add('is-visible'); + + /** + * 创建加载动画元素 + * @returns {HTMLElement} 创建的元素 + */ + function createElement() { + const el = document.createElement('div'); + el.id = OVERLAY_ID; + el.className = 'loading-overlay'; + el.innerHTML = createSkeletonHTML(); + return el; + } + + /** + * 获取或创建加载动画元素 + * @returns {HTMLElement} 加载动画元素 + */ + function getElement() { + if (!overlayElement) { + overlayElement = document.getElementById(OVERLAY_ID); + } + if (!overlayElement) { + overlayElement = createElement(); + document.body.appendChild(overlayElement); + } + return overlayElement; + } + + /** + * 显示加载动画 + */ + function show() { + // 清除可能存在的隐藏定时器 + if (hideTimer) { + clearTimeout(hideTimer); + hideTimer = null; + } + + const el = getElement(); + el.classList.remove(CLASS_HIDING); + + // 强制重排以确保动画生效 + void el.offsetWidth; + + el.classList.add(CLASS_VISIBLE); + } + + /** + * 隐藏加载动画 + */ + function hide() { + const el = document.getElementById(OVERLAY_ID); + if (!el) return; + + el.classList.add(CLASS_HIDING); + + // 动画结束后清理状态 + hideTimer = setTimeout(function() { + el.classList.remove(CLASS_VISIBLE, CLASS_HIDING); + hideTimer = null; + }, ANIMATION_DURATION); + } + + /** + * 销毁加载动画元素 + */ + function destroy() { + if (hideTimer) { + clearTimeout(hideTimer); + hideTimer = null; + } + if (overlayElement && overlayElement.parentNode) { + overlayElement.parentNode.removeChild(overlayElement); + overlayElement = null; + } + } + + // 公开 API + return { + show: show, + hide: hide, + destroy: destroy + }; +})(); + +/** + * 显示页面加载动画(向后兼容) + */ +function showLoadingOverlay() { + LoadingOverlay.show(); } /** - * 隐藏页面加载动画遮罩 + * 隐藏页面加载动画(向后兼容) */ function hideLoadingOverlay() { - let el = document.getElementById('article-loading-overlay'); - if (!el) return; - el.classList.add('is-hiding'); - setTimeout(function() { - el.classList.remove('is-visible'); - el.classList.remove('is-hiding'); - }, 300); + LoadingOverlay.hide(); } function startPageTransition() { document.documentElement.classList.add('page-transition-enter'); @@ -3479,7 +3573,7 @@ $(document).on("click", ".reference-link , .reference-list-backlink" , function( }, 1); }); -/*Tags Dialog pjax 加载后自动关闭/ +/*Tags Dialog pjax 加载后自动关闭 */ $(document).on("click" , "#blog_tags .tag" , function(){ $("#blog_tags button.close").trigger("click"); }); diff --git a/style.css b/style.css index 293f08a..7bb5342 100644 --- a/style.css +++ b/style.css @@ -17047,13 +17047,39 @@ article img.loaded, .post-thumbnail img.loaded, article img:not([loading="lazy"] /* 11. 骨架屏和加载动画 */ @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); } -/* ---------- 加载动画 ---------- */ -@keyframes modernSpinnerRotate { to { transform: rotate(360deg); } } -.loading-spinner { width: 24px; height: 24px; border: 2px solid var(--color-border); border-top-color: var(--themecolor); border-radius: 50%; animation: modernSpinnerRotate 0.8s linear infinite; } -#page-loading-bar { position: fixed; top: 0; left: 0; height: 3px; background: var(--themecolor-gradient); z-index: 9999; transition: width var(--animation-fast) var(--ease-out-expo); box-shadow: 0 0 10px rgba(var(--themecolor-rgbstr), 0.5); } +/* ========================================================================== + 页面加载动画 + ========================================================================== */ -/* 加载遮罩层 */ -#article-loading-overlay { +/* ---------- 基础旋转器 ---------- */ +@keyframes modernSpinnerRotate { + to { + transform: rotate(360deg); + } +} +.loading-spinner { + width: 24px; + height: 24px; + border: 2px solid var(--color-border); + border-top-color: var(--themecolor); + border-radius: 50%; + animation: modernSpinnerRotate 0.8s linear infinite; +} + +/* ---------- 顶部进度条 ---------- */ +#page-loading-bar { + position: fixed; + top: 0; + left: 0; + height: 3px; + background: var(--themecolor-gradient); + z-index: 9999; + transition: width var(--animation-fast) var(--ease-out-expo); + box-shadow: 0 0 10px rgba(var(--themecolor-rgbstr), 0.5); +} + +/* ---------- 加载遮罩层 ---------- */ +.loading-overlay { position: fixed; inset: 0; z-index: 9998; @@ -17068,149 +17094,170 @@ article img.loaded, .post-thumbnail img.loaded, article img:not([loading="lazy"] -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 #article-loading-overlay { +html.darkmode .loading-overlay { background: rgba(0, 0, 0, 0.5); } -#article-loading-overlay.is-visible { +.loading-overlay.is-visible { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity var(--animation-normal) var(--ease-standard), visibility 0s; } -#article-loading-overlay.is-hiding { +.loading-overlay.is-hiding { opacity: 0; visibility: visible; pointer-events: none; transition: opacity var(--animation-normal) var(--ease-standard); } -/* 加载内容容器 */ -#article-loading-overlay .overlay-content { +/* ---------- 内容容器 ---------- */ +.loading-overlay-content { width: min(720px, 90vw); opacity: 0; transform: translate3d(0, 12px, 0) scale(0.98); transition: opacity var(--animation-normal) var(--ease-standard), transform var(--animation-normal) var(--ease-emphasized-decelerate); } -#article-loading-overlay.is-visible .overlay-content { +.loading-overlay.is-visible .loading-overlay-content { opacity: 1; transform: translate3d(0, 0, 0) scale(1); } -/* 卡片容器 */ -#article-loading-overlay .overlay-card { +/* ---------- 卡片容器 ---------- */ +.loading-card { background: var(--color-foreground); border-radius: var(--card-radius); overflow: hidden; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); margin-bottom: 24px; } -html.darkmode #article-loading-overlay .overlay-card { +html.darkmode .loading-card { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } -/* 缩略图 */ -#article-loading-overlay .overlay-thumb { +/* ---------- 缩略图骨架 ---------- */ +.loading-thumb { position: relative; width: 100%; height: 240px; overflow: hidden; + 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; } -#article-loading-overlay .overlay-thumb-shimmer { +.loading-shimmer { position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); - animation: shimmer 2s infinite; + animation: shimmerMove 2s infinite; } -html.darkmode #article-loading-overlay .overlay-thumb-shimmer { +html.darkmode .loading-shimmer { background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); } -@keyframes shimmer { - to { left: 100%; } +@keyframes shimmerMove { + to { + left: 100%; + } +} +@keyframes skeletonPulse { + 0%, 100% { + background-position: 0% 0%; + } + 50% { + background-position: 100% 0%; + } } -/* 卡片主体 */ -#article-loading-overlay .overlay-body { +/* ---------- 卡片主体 ---------- */ +.loading-body { padding: 24px; } -/* 元信息区域 */ -#article-loading-overlay .overlay-meta { +/* ---------- 元信息区域 ---------- */ +.loading-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } -#article-loading-overlay .overlay-avatar { +.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; } -#article-loading-overlay .overlay-meta-text { +.loading-meta-text { flex: 1; display: flex; flex-direction: column; gap: 6px; } -#article-loading-overlay .overlay-meta-line { +.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; } -/* 标题 */ -#article-loading-overlay .overlay-title { +/* ---------- 标题骨架 ---------- */ +.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; } -/* 文本行 */ -#article-loading-overlay .overlay-text { +/* ---------- 文本行骨架 ---------- */ +.loading-text { margin-bottom: 16px; } -#article-loading-overlay .overlay-row { +.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; } -/* 标签 */ -#article-loading-overlay .overlay-tags { +/* ---------- 标签骨架 ---------- */ +.loading-tags { display: flex; gap: 8px; flex-wrap: wrap; } -#article-loading-overlay .overlay-tag { +.loading-tag { width: 60px; height: 24px; border-radius: 12px; -} - -/* 骨架屏动画 */ -#article-loading-overlay .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.8s ease-in-out infinite; + animation: skeletonPulse 1.8s ease-in-out infinite; } -/* 加载旋转器 */ -#article-loading-overlay .overlay-spinner { +/* ---------- 加载旋转器 ---------- */ +.loading-spinner-wrapper { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 20px; } -#article-loading-overlay .spinner-ring { +.loading-spinner { position: relative; width: 56px; height: 56px; } -#article-loading-overlay .spinner-ring::before { +.loading-spinner::before { content: ''; position: absolute; inset: 0; @@ -17218,7 +17265,7 @@ html.darkmode #article-loading-overlay .overlay-thumb-shimmer { border: 3px solid var(--color-border); opacity: 0.2; } -#article-loading-overlay .spinner-ring-inner { +.loading-spinner-ring { position: absolute; inset: 0; border-radius: 50%; @@ -17228,34 +17275,38 @@ html.darkmode #article-loading-overlay .overlay-thumb-shimmer { animation: spinnerRotate 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite; } @keyframes spinnerRotate { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } -/* 加载文字 */ -#article-loading-overlay .spinner-text { +/* ---------- 加载文字 ---------- */ +.loading-text-hint { color: var(--color-font); font-size: 15px; font-weight: 500; letter-spacing: 0.3px; } -/* 加载点动画 */ -#article-loading-overlay .spinner-dots { +/* ---------- 加载点动画 ---------- */ +.loading-dots { display: flex; gap: 6px; } -#article-loading-overlay .spinner-dots .dot { +.loading-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--themecolor); animation: dotBounce 1.4s infinite ease-in-out both; } -#article-loading-overlay .spinner-dots .dot:nth-child(1) { +.loading-dot:nth-child(1) { animation-delay: -0.32s; } -#article-loading-overlay .spinner-dots .dot:nth-child(2) { +.loading-dot:nth-child(2) { animation-delay: -0.16s; } @keyframes dotBounce { @@ -17269,40 +17320,40 @@ html.darkmode #article-loading-overlay .overlay-thumb-shimmer { } } -/* 响应式适配 */ +/* ---------- 响应式适配 ---------- */ @media (max-width: 768px) { - #article-loading-overlay .overlay-thumb { + .loading-thumb { height: 180px; } - #article-loading-overlay .overlay-body { + .loading-body { padding: 20px; } - #article-loading-overlay .overlay-title { + .loading-title { height: 24px; width: 90%; } - #article-loading-overlay .spinner-ring { + .loading-spinner { width: 48px; height: 48px; } - #article-loading-overlay .spinner-text { + .loading-text-hint { font-size: 14px; } } @media (max-width: 480px) { - #article-loading-overlay .overlay-content { + .loading-overlay-content { width: 95vw; } - #article-loading-overlay .overlay-thumb { + .loading-thumb { height: 160px; } - #article-loading-overlay .overlay-body { + .loading-body { padding: 16px; } - #article-loading-overlay .overlay-meta { + .loading-meta { gap: 10px; } - #article-loading-overlay .overlay-avatar { + .loading-avatar { width: 36px; height: 36px; }