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

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 Search from "astro-pagefind/components/Search";
import ThemeToggle from "@/components/ThemeToggle.astro";
import "@/styles/header.css";
// 获取当前路径
const currentPath = Astro.url.pathname;

View File

@ -95,6 +95,49 @@ const {
<script is:inline>
// 立即执行主题初始化,采用"无闪烁"加载方式
(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 {
// 获取系统首选主题
const getSystemTheme = () => {
@ -121,6 +164,8 @@ const {
// 立即设置文档主题在DOM渲染前应用避免闪烁
document.documentElement.dataset.theme = theme;
// 确保同步classList提高兼容性
document.documentElement.classList.toggle('dark', theme === 'dark');
// 监听系统主题变化(只有当主题设为跟随系统时才响应)
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
@ -130,14 +175,37 @@ const {
if (!localStorage.getItem("theme")) {
const newTheme = e.matches ? "dark" : "light";
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) {
// 出错时应用默认浅色主题,确保页面正常显示
document.documentElement.dataset.theme = "light";
document.documentElement.classList.remove('dark');
}
})();
</script>
@ -156,7 +224,7 @@ const {
/>
<!-- 预获取脚本 -->
<script>
<script is:inline>
// 在DOM加载完成后执行
document.addEventListener("astro:page-load", () => {
// 获取所有视口预获取链接

View File

@ -914,36 +914,5 @@ const {
}, 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>

View File

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

View File

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

View File

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

View File

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