优化事件监听机制避免泄漏

This commit is contained in:
lsy 2025-04-25 18:25:05 +08:00
parent 3146fc99cd
commit 8656f037bd
7 changed files with 125 additions and 81 deletions

View File

@ -2,7 +2,7 @@
import { SITE_NAME, NAV_LINKS } from "@/consts.ts"; import { SITE_NAME, NAV_LINKS } from "@/consts.ts";
import Search from "astro-pagefind/components/Search"; import Search from "astro-pagefind/components/Search";
import ThemeToggle from "@/components/ThemeToggle.astro"; import ThemeToggle from "@/components/ThemeToggle.astro";
import "@/styles/header.css";
// 获取当前路径 // 获取当前路径
const currentPath = Astro.url.pathname; const currentPath = Astro.url.pathname;

View File

@ -95,6 +95,49 @@ const {
<script is:inline> <script is:inline>
// 立即执行主题初始化,采用"无闪烁"加载方式 // 立即执行主题初始化,采用"无闪烁"加载方式
(function () { (function () {
// 存储事件监听器,便于清理
const listeners = [];
// 添加事件监听器并记录,方便后续统一清理
function addListener(element, eventType, handler, options) {
if (!element) return null;
element.addEventListener(eventType, handler, options);
listeners.push({ element, eventType, handler, options });
return handler;
}
// 清理函数 - 移除所有事件监听器
function cleanup() {
listeners.forEach(({ element, eventType, handler, options }) => {
try {
element.removeEventListener(eventType, handler, options);
} catch (err) {
// 忽略错误
}
});
// 清空数组
listeners.length = 0;
}
// 注册清理函数 - 确保在页面转换前清理事件
function registerCleanup() {
const cleanupEvents = [
"astro:before-preparation",
"astro:before-swap",
"swup:willReplaceContent"
];
// 为每个事件注册一次性清理函数
cleanupEvents.forEach((eventName) => {
document.addEventListener(eventName, cleanup, { once: true });
});
// 页面卸载时清理
window.addEventListener("beforeunload", cleanup, { once: true });
}
try { try {
// 获取系统首选主题 // 获取系统首选主题
const getSystemTheme = () => { const getSystemTheme = () => {
@ -121,6 +164,8 @@ const {
// 立即设置文档主题在DOM渲染前应用避免闪烁 // 立即设置文档主题在DOM渲染前应用避免闪烁
document.documentElement.dataset.theme = theme; document.documentElement.dataset.theme = theme;
// 确保同步classList提高兼容性
document.documentElement.classList.toggle('dark', theme === 'dark');
// 监听系统主题变化(只有当主题设为跟随系统时才响应) // 监听系统主题变化(只有当主题设为跟随系统时才响应)
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
@ -130,14 +175,37 @@ const {
if (!localStorage.getItem("theme")) { if (!localStorage.getItem("theme")) {
const newTheme = e.matches ? "dark" : "light"; const newTheme = e.matches ? "dark" : "light";
document.documentElement.dataset.theme = newTheme; document.documentElement.dataset.theme = newTheme;
document.documentElement.classList.toggle('dark', e.matches);
} }
}; };
// 添加系统主题变化监听 // 添加系统主题变化监听
mediaQuery.addEventListener("change", handleMediaChange); addListener(mediaQuery, "change", handleMediaChange);
// 注册清理函数
registerCleanup();
// 监听页面转换事件,确保在页面转换后重新初始化
function onPageTransition() {
// 重新初始化主题
if (storedTheme) {
document.documentElement.dataset.theme = storedTheme;
document.documentElement.classList.toggle('dark', storedTheme === 'dark');
} else {
const systemTheme = getSystemTheme();
document.documentElement.dataset.theme = systemTheme;
document.documentElement.classList.toggle('dark', systemTheme === 'dark');
}
}
// 设置页面转换事件监听
document.addEventListener("astro:page-load", onPageTransition);
document.addEventListener("astro:after-swap", onPageTransition);
} catch (error) { } catch (error) {
// 出错时应用默认浅色主题,确保页面正常显示 // 出错时应用默认浅色主题,确保页面正常显示
document.documentElement.dataset.theme = "light"; document.documentElement.dataset.theme = "light";
document.documentElement.classList.remove('dark');
} }
})(); })();
</script> </script>
@ -156,7 +224,7 @@ const {
/> />
<!-- 预获取脚本 --> <!-- 预获取脚本 -->
<script> <script is:inline>
// 在DOM加载完成后执行 // 在DOM加载完成后执行
document.addEventListener("astro:page-load", () => { document.addEventListener("astro:page-load", () => {
// 获取所有视口预获取链接 // 获取所有视口预获取链接

View File

@ -914,36 +914,5 @@ const {
}, 0); }, 0);
} }
// 全局暴露主题切换函数和配置,方便调试和高级用法
window.__themeToggle = {
modes: TRANSITION_MODES,
getMode: getThemeTransitionMode,
setMode: saveThemeTransitionMode,
// 添加新的帮助方法:切换动画模式
toggleMode: function() {
const currentMode = getThemeTransitionMode();
const modes = Object.values(TRANSITION_MODES);
const currentIndex = modes.indexOf(currentMode);
const nextIndex = (currentIndex + 1) % modes.length;
const nextMode = modes[nextIndex];
saveThemeTransitionMode(nextMode);
return nextMode;
},
// 描述当前模式
describeModeEffect: function() {
const mode = getThemeTransitionMode();
const currentTheme = document.documentElement.dataset.theme || 'light';
const nextTheme = currentTheme === 'light' ? 'dark' : 'light';
// 确定将使用的动画类型
const animationType = determineAnimationType(mode, currentTheme, nextTheme);
if (animationType === TRANSITION_MODES.EXPAND) {
return `当前模式: ${mode}, ${currentTheme}→${nextTheme} 将使用扩散效果`;
} else {
return `当前模式: ${mode}, ${currentTheme}→${nextTheme} 将使用收缩效果`;
}
}
};
})(); })();
</script> </script>

View File

@ -4,6 +4,7 @@ import { getSpecialPath } from "@/content.config";
import Layout from "@/components/Layout.astro"; import Layout from "@/components/Layout.astro";
import Breadcrumb from "@/components/Breadcrumb.astro"; import Breadcrumb from "@/components/Breadcrumb.astro";
import { ARTICLE_EXPIRY_CONFIG } from "@/consts"; import { ARTICLE_EXPIRY_CONFIG } from "@/consts";
import "@/styles/content-styles.css";
// 添加这一行告诉Astro预渲染这个页面 // 添加这一行告诉Astro预渲染这个页面
export const prerender = true; export const prerender = true;

View File

@ -324,21 +324,18 @@ function getArticleUrl(articleId: string) {
<script is:inline> <script is:inline>
// 使用"进入→绑定→退出完全清理"模式 // 使用"进入→绑定→退出完全清理"模式
(function () { (function () {
// 脚本实例ID用于跟踪当前实例 // 统一管理所有事件监听器
const SCRIPT_INSTANCE_ID = Date.now() + '-' + Math.random().toString(36).substring(2, 9); const listeners = [];
// 标记脚本是否已销毁 // 标记脚本是否已销毁
let isDestroyed = false; let isDestroyed = false;
// 统一管理所有事件监听器
const listeners = [];
// 添加事件监听器并记录,方便后续统一清理 // 添加事件监听器并记录,方便后续统一清理
function addListener(element, eventType, handler, options) { function addListener(element, eventType, handler, options) {
if (!element) return null; if (!element || isDestroyed) return null;
element.addEventListener(eventType, handler, options); element.addEventListener(eventType, handler, options);
listeners.push({ element, eventType, handler }); listeners.push({ element, eventType, handler, options });
return handler; return handler;
} }
@ -351,9 +348,9 @@ function getArticleUrl(articleId: string) {
isDestroyed = true; isDestroyed = true;
// 移除所有监听器 // 移除所有监听器
listeners.forEach(({ element, eventType, handler }) => { listeners.forEach(({ element, eventType, handler, options }) => {
try { try {
element.removeEventListener(eventType, handler); element.removeEventListener(eventType, handler, options);
} catch (err) { } catch (err) {
// 忽略错误 // 忽略错误
} }
@ -362,15 +359,8 @@ function getArticleUrl(articleId: string) {
// 清空数组 // 清空数组
listeners.length = 0; listeners.length = 0;
// 移除页面转换监听器 // 移除页面可见性监听
document.removeEventListener("astro:after-swap", onPageTransition); document.removeEventListener('visibilitychange', onPageVisibilityChange);
document.removeEventListener("astro:page-load", onPageTransition);
document.removeEventListener("swup:contentReplaced", onPageTransition);
// 从全局范围中移除自身引用
if (window.__filteredPageInstances) {
window.__filteredPageInstances = window.__filteredPageInstances.filter(id => id !== SCRIPT_INSTANCE_ID);
}
} }
// 跟踪页面离开,确保完全清理 // 跟踪页面离开,确保完全清理
@ -403,7 +393,7 @@ function getArticleUrl(articleId: string) {
endDate: urlParams.get('endDate') || '' endDate: urlParams.get('endDate') || ''
}; };
// 储存所有文章数据(全局缓存) // 储存所有文章数据
let allArticles = []; let allArticles = [];
let isArticlesLoaded = false; let isArticlesLoaded = false;
@ -1362,54 +1352,71 @@ function getArticleUrl(articleId: string) {
return; return;
} }
// 如果仍在筛选页面但脚本已销毁,重新初始化 // 如果当前页面是筛选页面,重新初始化
if (isDestroyed) { if (isDestroyed) {
isDestroyed = false; isDestroyed = false;
init();
return;
} }
// 如果仍在筛选页面且脚本未销毁,可能是路由切换,重新初始化 // 重新初始化
init(); init();
} }
// 初始化函数 // 初始化函数
function init() { function init() {
// 设置功能 // 如果已销毁,立即退出
if (isDestroyed) return;
// 首先清理可能存在的旧监听器
listeners.forEach(({ element, eventType, handler, options }) => {
try {
element.removeEventListener(eventType, handler, options);
} catch (err) {
// 忽略错误
}
});
listeners.length = 0;
// 设置筛选功能
setupArticlesFilter(); setupArticlesFilter();
// 注册清理函数 // 注册清理函数
const cleanupEvents = [ registerCleanup();
"astro:before-preparation",
"astro:before-swap",
"swup:willReplaceContent",
"beforeunload"
];
// 为每个事件类型注册一次性清理
cleanupEvents.forEach(eventType => {
const target = eventType === "beforeunload" ? window : document;
target.addEventListener(eventType, () => {
cleanup();
}, { once: true });
});
// 添加页面可见性变化监听 // 添加页面可见性变化监听
document.removeEventListener('visibilitychange', onPageVisibilityChange);
document.addEventListener('visibilitychange', onPageVisibilityChange); document.addEventListener('visibilitychange', onPageVisibilityChange);
} }
// 页面转换时检查 // 注册清理函数 - 确保在页面转换前清理事件
function registerCleanup() {
const cleanupEvents = [
"astro:before-preparation",
"astro:before-swap",
"swup:willReplaceContent"
];
// 为每个事件注册一次性清理函数
cleanupEvents.forEach((eventName) => {
document.addEventListener(eventName, cleanup, { once: true });
});
// 页面卸载时清理
window.addEventListener("beforeunload", cleanup, { once: true });
}
// 监听页面转换
document.removeEventListener("astro:after-swap", onPageTransition);
document.removeEventListener("astro:page-load", onPageTransition);
document.removeEventListener("swup:contentReplaced", onPageTransition);
document.addEventListener("astro:after-swap", onPageTransition); document.addEventListener("astro:after-swap", onPageTransition);
document.addEventListener("astro:page-load", onPageTransition); document.addEventListener("astro:page-load", onPageTransition);
document.addEventListener("swup:contentReplaced", onPageTransition); document.addEventListener("swup:contentReplaced", onPageTransition);
// 初始化 // 在页面加载后初始化
if (document.readyState === "loading") { if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init, { document.addEventListener("DOMContentLoaded", init, { once: true });
once: true,
});
} else { } else {
// 使用setTimeout确保处于事件队列末尾避免可能的事件冲突
setTimeout(init, 0); setTimeout(init, 0);
} }
})(); })();

View File

@ -1,3 +1,5 @@
@import "./table-styles.css";
/* 增强列表样式 */ /* 增强列表样式 */
.prose ul { .prose ul {
list-style-type: disc; list-style-type: disc;

View File

@ -1,7 +1,4 @@
@import "tailwindcss"; @import "tailwindcss";
@import "./content-styles.css";
@import "./table-styles.css";
@import "./header.css";
/* 定义深色模式选择器 */ /* 定义深色模式选择器 */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));