diff --git a/src/components/ThemeToggle.astro b/src/components/ThemeToggle.astro index a2da222..8f65e6b 100644 --- a/src/components/ThemeToggle.astro +++ b/src/components/ThemeToggle.astro @@ -494,28 +494,7 @@ import "../styles/theme-toggle.css"; { element: document, eventType: 'astro:before-swap', handler: resetThemeToggleState }, { element: document, eventType: 'astro:after-swap', handler: resetThemeToggleState } ); - - // 全局点击事件拦截器 - const globalClickHandler = (e) => { - if (!e || !e.target) return; - - const button = e.target.closest('#theme-toggle-button'); - - if (button && (isTransitioning(button) || !canToggleTheme())) { - e.stopPropagation(); - e.preventDefault(); - } - }; - - // 在document级别添加事件捕获 - document.addEventListener('click', globalClickHandler, { capture: true }); - allListeners.push({ - element: document, - eventType: 'click', - handler: globalClickHandler, - options: { capture: true } - }); - + // 获取系统首选主题 const getSystemTheme = () => { return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; diff --git a/src/components/WorldHeatmap.tsx b/src/components/WorldHeatmap.tsx index 8980b12..cfcdd01 100644 --- a/src/components/WorldHeatmap.tsx +++ b/src/components/WorldHeatmap.tsx @@ -249,11 +249,109 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { } }, [wasmModule, visitedPlaces, mapData.worldData, mapData.chinaData]); + // 添加对页面转换事件的监听 + useEffect(() => { + // 统一存储所有事件监听器 + const allListeners: Array<{ + element: EventTarget; + eventType: string; + handler: EventListener; + options?: boolean | AddEventListenerOptions; + }> = []; + + // 添加事件监听器并记录 + const addListener = ( + element: EventTarget, + eventType: string, + handler: EventListener, + options?: boolean | AddEventListenerOptions + ) => { + if (!element) { + console.warn("[WorldHeatmap] 尝试为不存在的元素添加事件"); + return null; + } + + element.addEventListener(eventType, handler, options); + allListeners.push({ element, eventType, handler, options }); + return handler; + }; + + // 监听页面转换事件的处理函数 + const handlePageTransition = () => { + // 1. 清理所有注册的事件监听器 + allListeners.forEach(({ element, eventType, handler, options }) => { + try { + element.removeEventListener(eventType, handler, options); + } catch (err) { + console.error(`[WorldHeatmap] 清理事件 ${eventType} 出错:`, err); + } + }); + + // 2. 清理Three.js资源 + if (sceneRef.current) { + // 取消动画帧 + if (sceneRef.current.animationId !== null) { + cancelAnimationFrame(sceneRef.current.animationId); + sceneRef.current.animationId = null; + } + + try { + // 优先清理Three.js对象 + if (sceneRef.current.scene) { + sceneRef.current.scene.clear(); + } + + // 清理控制器 + if (sceneRef.current.controls) { + sceneRef.current.controls.dispose(); + } + + // 移除标签渲染器 + if (sceneRef.current.labelRenderer) { + if (sceneRef.current.labelRenderer.domElement.parentNode) { + sceneRef.current.labelRenderer.domElement.remove(); + } + } + + // 最后处理WebGL渲染器 + sceneRef.current.renderer.dispose(); + sceneRef.current.renderer.forceContextLoss(); + if (sceneRef.current.renderer.domElement.parentNode) { + sceneRef.current.renderer.domElement.remove(); + } + } catch (err) { + console.error("[WorldHeatmap] 清理Three.js资源错误:", err); + } + } + + // 强制将场景引用置为null,避免后续访问 + sceneRef.current = null; + }; + + // 添加页面转换事件监听 + addListener(document, "page-transition", handlePageTransition); + addListener(document, "astro:before-swap", handlePageTransition); + addListener(window, "beforeunload", handlePageTransition); + + // 清理函数 - 这个通常不会执行,因为页面转换时组件会被销毁 + // 但为了完整性,我们仍然添加此清理逻辑 + return () => { + allListeners.forEach(({ element, eventType, handler, options }) => { + try { + element.removeEventListener(eventType, handler, options); + } catch (err) { + console.error(`[WorldHeatmap] 清理事件 ${eventType} 出错:`, err); + } + }); + }; + }, []); + + // 主要Three.js初始化和清理 useEffect(() => { if (!containerRef.current || !wasmModule || !wasmReady || !geoProcessor) { return; } - + // 清理之前的场景 if (sceneRef.current) { if (sceneRef.current.animationId !== null) { @@ -286,13 +384,13 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { // 根据当前模式设置颜色 const getColors = () => { return { - earthBase: isDarkMode ? "#111827" : "#2a4d69", // 深色模式保持深色,浅色模式改为更柔和的蓝色 - visited: isDarkMode ? "#065f46" : "#34d399", // 访问过的颜色更鲜明 - border: isDarkMode ? "#6b7280" : "#e0e0e0", // 边界颜色调整为更亮的浅灰色 - visitedBorder: isDarkMode ? "#10b981" : "#0d9488", // 访问过的边界颜色 - chinaBorder: isDarkMode ? "#f87171" : "#ef4444", // 中国边界使用红色 - text: isDarkMode ? "#f9fafb" : "#1f2937", // 文本颜色对比更强 - highlight: isDarkMode ? "#fcd34d" : "#60a5fa", // 高亮颜色改为浅蓝色,更配合背景 + earthBase: isDarkMode ? "#111827" : "#2a4d69", + visited: isDarkMode ? "#065f46" : "#34d399", + border: isDarkMode ? "#6b7280" : "#e0e0e0", + visitedBorder: isDarkMode ? "#10b981" : "#0d9488", + chinaBorder: isDarkMode ? "#f87171" : "#ef4444", + text: isDarkMode ? "#f9fafb" : "#1f2937", + highlight: isDarkMode ? "#fcd34d" : "#60a5fa", }; }; @@ -321,7 +419,7 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { const earthMaterial = createMaterial( colors.earthBase, FrontSide, - isDarkMode ? 0.9 : 0.9, // 调整明亮模式下的不透明度 + isDarkMode ? 0.9 : 0.9, ); const earth = new Mesh(earthGeometry, earthMaterial); earth.renderOrder = 1; @@ -330,13 +428,13 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { // 添加光源 const ambientLight = new AmbientLight( 0xffffff, - isDarkMode ? 0.7 : 0.85, // 微调明亮模式下的光照强度 + isDarkMode ? 0.7 : 0.85, ); scene.add(ambientLight); const directionalLight = new DirectionalLight( - isDarkMode ? 0xeeeeff : 0xffffff, // 恢复明亮模式下的纯白光源 - isDarkMode ? 0.6 : 0.65, // 微调明亮模式下的定向光强度 + isDarkMode ? 0xeeeeff : 0xffffff, + isDarkMode ? 0.6 : 0.65, ); directionalLight.position.set(5, 3, 5); scene.add(directionalLight); @@ -381,10 +479,10 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { // 添加控制器 const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; - controls.dampingFactor = 0.25; // 大幅增加阻尼因子,从0.1到0.25提高稳定性 - controls.rotateSpeed = 0.2; // 降低旋转速度,提高稳定性 + controls.dampingFactor = 0.25; + controls.rotateSpeed = 0.2; controls.autoRotate = true; - controls.autoRotateSpeed = 0.3; // 降低自动旋转速度 + controls.autoRotateSpeed = 0.3; controls.minDistance = 5; controls.maxDistance = 15; @@ -446,9 +544,9 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { const lineMaterial = new LineBasicMaterial({ color: borderColor, - linewidth: is_visited ? 1.8 : 1.2, // 微调线条宽度,保持已访问区域更明显 + linewidth: is_visited ? 1.8 : 1.2, transparent: true, - opacity: is_visited ? 0.95 : 0.85, // 调整不透明度,使边界明显但不突兀 + opacity: is_visited ? 0.95 : 0.85, }); const line = new Line(lineGeometry, lineMaterial); @@ -836,32 +934,77 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { mounted = false; // 执行所有保存的清理函数 - cleanupFunctions.forEach(fn => fn()); + cleanupFunctions.forEach((fn, index) => { + try { + fn(); + } catch (err) { + console.error(`[WorldHeatmap] 清理函数执行错误:`, err); + } + }); - // 清理资源和事件监听器 + // 清理资源和事件监听器 - 改进错误处理 if (sceneRef.current) { - // 取消动画帧 - if (sceneRef.current.animationId !== null) { - cancelAnimationFrame(sceneRef.current.animationId); + // 1. 取消动画帧 - 单独错误处理 + try { + if (sceneRef.current.animationId !== null) { + cancelAnimationFrame(sceneRef.current.animationId); + sceneRef.current.animationId = null; + } + } catch (err) { + console.error("[WorldHeatmap] 清理动画帧错误:", err); } - // 处理渲染器的处理 - sceneRef.current.renderer.dispose(); - sceneRef.current.renderer.forceContextLoss(); - sceneRef.current.renderer.domElement.remove(); - - // 移除标签渲染器 - if (sceneRef.current.labelRenderer) { - sceneRef.current.labelRenderer.domElement.remove(); + // 2. 清理渲染器 - 单独错误处理 + try { + if (sceneRef.current.renderer) { + sceneRef.current.renderer.dispose(); + sceneRef.current.renderer.forceContextLoss(); + if (sceneRef.current.renderer.domElement && sceneRef.current.renderer.domElement.parentNode) { + sceneRef.current.renderer.domElement.remove(); + } + } + } catch (err) { + console.error("[WorldHeatmap] 清理渲染器错误:", err); } - // 释放控制器 - if (sceneRef.current.controls) { - sceneRef.current.controls.dispose(); + // 3. 移除标签渲染器 - 单独错误处理 + try { + if (sceneRef.current.labelRenderer) { + if (sceneRef.current.labelRenderer.domElement && sceneRef.current.labelRenderer.domElement.parentNode) { + sceneRef.current.labelRenderer.domElement.remove(); + } + } + } catch (err) { + console.error("[WorldHeatmap] 清理标签渲染器错误:", err); + } + + // 4. 释放控制器 - 单独错误处理 + try { + if (sceneRef.current.controls) { + sceneRef.current.controls.dispose(); + } + } catch (err) { + console.error("[WorldHeatmap] 清理控制器错误:", err); + } + + // 5. 清理场景 - 单独错误处理 + try { + if (sceneRef.current.scene) { + sceneRef.current.scene.clear(); + } + } catch (err) { + console.error("[WorldHeatmap] 清理场景错误:", err); + } + + // 6. 最后将引用置为null + try { + sceneRef.current = null; + } catch (err) { + console.error("[WorldHeatmap] 重置场景引用错误:", err); } } }; - }, [visitedPlaces, theme, wasmReady, geoProcessor]); // 添加geoProcessor依赖 + }, [visitedPlaces, theme, wasmReady, geoProcessor]); return (