fix: 彻底修复GSAP动画泄漏导致的自动转场问题

关键修复:
- 在 SceneManager 中保存 animationFrameId 和 introTimeline 引用
- 在 Transition 中保存 timeline 引用
- dispose() 时彻底清理所有 GSAP 动画:
  * 停止 requestAnimationFrame 循环
  * 杀掉开场动画 timeline
  * 杀掉转场动画 timeline
  * 杀掉所有针对相机、地球的补间动画
- 添加 Transition.dispose() 方法
- 防止旧动画回调触发导致意外转场

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
KQL
2025-12-04 20:02:38 +08:00
parent 4bf7dcde79
commit b92bb4985d
2 changed files with 54 additions and 4 deletions

View File

@@ -30,6 +30,10 @@ export class SceneManager {
this.earthModel = null;
this.transition = null;
// 动画控制
this.animationFrameId = null;
this.introTimeline = null;
this.init();
}
@@ -97,12 +101,15 @@ export class SceneManager {
startIntroSequence(uiElements) {
const animConfig = CONFIG.animation.intro;
const tl = gsap.timeline({
// 保存 timeline 引用,以便在 dispose 时清理
this.introTimeline = gsap.timeline({
onComplete: () => {
this.isIntro = false; // 动画结束,允许交互
}
});
const tl = this.introTimeline;
// A. 摄像机缓慢推进 (从 z=100 -> z=16)
tl.to(this.camera.position, {
z: CONFIG.scene.camera.defaultZ,
@@ -279,7 +286,7 @@ export class SceneManager {
// 动画循环
animate() {
requestAnimationFrame(() => this.animate());
this.animationFrameId = requestAnimationFrame(() => this.animate());
// 如果不在转场中
if (!this.transition || !this.transition.isActive()) {
@@ -299,6 +306,31 @@ export class SceneManager {
// 销毁场景
dispose() {
// 停止动画循环
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = null;
}
// 杀掉所有正在运行的 GSAP 动画
if (this.introTimeline) {
this.introTimeline.kill();
this.introTimeline = null;
}
// 杀掉转场相关的 GSAP 动画(杀掉所有针对相机、地球等对象的动画)
if (this.camera) {
gsap.killTweensOf(this.camera.position);
}
if (this.earthModel) {
const earthGroup = this.earthModel.getGroup();
const clouds = this.earthModel.getClouds();
const atmosphere = this.earthModel.getAtmosphere();
if (earthGroup) gsap.killTweensOf(earthGroup.rotation);
if (clouds) gsap.killTweensOf(clouds.material);
if (atmosphere) gsap.killTweensOf(atmosphere.material);
}
// 移除事件监听器,防止内存泄漏和重复触发
if (this.eventHandlers) {
window.removeEventListener('mousemove', this.eventHandlers.mouseMove);
@@ -308,6 +340,12 @@ export class SceneManager {
window.removeEventListener('resize', this.eventHandlers.resize);
}
// 清理转场动画
if (this.transition) {
this.transition.dispose();
this.transition = null;
}
if (this.starSystem) this.starSystem.dispose();
if (this.earthModel) this.earthModel.dispose();
if (this.renderer) {

View File

@@ -11,6 +11,7 @@ export class Transition {
this.uiElements = uiElements; // { uiLayer, hint, speedLines, cloudFog }
this.onComplete = onComplete;
this.isTransitioning = false;
this.timeline = null; // 保存 timeline 引用以便清理
}
// 触发超空间跳跃转场
@@ -29,14 +30,16 @@ export class Transition {
const clouds = this.earthModel.getClouds();
const atmosphere = this.earthModel.getAtmosphere();
// 创建GSAP时间线
const tl = gsap.timeline({
// 创建GSAP时间线(保存引用以便清理)
this.timeline = gsap.timeline({
onComplete: () => {
this.isTransitioning = false;
if (this.onComplete) this.onComplete();
}
});
const tl = this.timeline;
// 转场配置
const warpConfig = CONFIG.warp;
const transConfig = CONFIG.animation.transition;
@@ -101,4 +104,13 @@ export class Transition {
isActive() {
return this.isTransitioning;
}
// 清理转场动画
dispose() {
if (this.timeline) {
this.timeline.kill();
this.timeline = null;
}
this.isTransitioning = false;
}
}