优化事件监听机制避免泄漏
This commit is contained in:
parent
3146fc99cd
commit
8656f037bd
@ -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;
|
||||||
|
|
||||||
|
@ -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", () => {
|
||||||
// 获取所有视口预获取链接
|
// 获取所有视口预获取链接
|
||||||
|
@ -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>
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "./table-styles.css";
|
||||||
|
|
||||||
/* 增强列表样式 */
|
/* 增强列表样式 */
|
||||||
.prose ul {
|
.prose ul {
|
||||||
list-style-type: disc;
|
list-style-type: disc;
|
||||||
|
@ -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] *));
|
||||||
|
Loading…
Reference in New Issue
Block a user