Skip to content

附录:隐形的面板 — 代码块全集

所有代码块均从正文中移出。编号对应正文中的引用标记。


1. reset() 方法 — 初始版本

typescript
reset(level: PopupLevel): void {
    this.levels = [level];
    this.activeIdx = 0;
    this.buildPanel(this.panels[0], level);
    this.panels[1].style.display = "none";
    this.inner.style.transform = "translateX(0)";
    this.inner.style.transition = "none";
    this.onAfterRender?.(level, this);
}

2. reset() 断点逐行执行

this.levels = [level];          → display 还是 ""
this.activeIdx = 0;             → display 还是 ""
this.buildPanel(this.panels[0], level);  → 构建内容,display 还是 ""
this.panels[1].style.display = "none";   → panels[1] 变 none,panels[0] 还是 ""
this.inner.style.transform = "translateX(0)"; → 还是 ""
this.inner.style.transition = "none";   → 还是 ""

3. _animateForward 交换面板引用

typescript
// 动画结束后,交换面板角色
const tmp = this.panels[0];
this.panels[0] = this.panels[1];
this.panels[1] = tmp;

4. 新 panels[1] 设为 none

typescript
this.panels[1].style.display = "none";

5. reset() 方法 — 重复出现(带注释版)

typescript
reset(level: PopupLevel): void {
    this.levels = [level];
    this.activeIdx = 0;
    this.buildPanel(this.panels[0], level);  // panels[0] 是元素 B,可见 ✅
    this.panels[1].style.display = "none";   // panels[1] 是元素 A,本来就是 none
    // ...
}

6. _animateBack 开头:恢复 panels[1] 可见

typescript
private _animateBack(newLevel: PopupLevel): void {
    this.transitioning = true;
    this.panels[1].style.display = "";  // ← 这里!
    this.buildPanel(this.panels[1], newLevel);
    // ...
}

7. 模型弹窗 DOM 结构(白屏时)

html
div#modelPopup.overlay.slide-menu.visible
  div.slide-inner
    div.slide-panel  ← panels[0], display: none
    div.slide-panel  ← panels[1], display: none

8. reset() 方法 — 第三次出现(带 transitioning 重置)

typescript
reset(level: PopupLevel): void {
    this.levels = [level];
    this.activeIdx = 0;
    this.transitioning = false;
    this.buildPanel(this.panels[0], level);
    this.panels[1].style.display = "none";
    this.inner.style.transform = "translateX(0)";
    this.inner.style.transition = "none";
    this.onAfterRender?.(level, this);
}

9. 日志:第一次打开模型弹窗

constructor: panels[0].display="", panels[1].display=""
reset: before: panels[0].display="", panels[1].display=""
reset: after: panels[0].display="", panels[1].display="none"

10. 日志:进入文件夹

_animateForward: before: panels[0].display="", panels[1].display="none"
_animateForward: 构建 panels[1] 内容
_animateForward: 动画开始
_animateForward: 动画结束,交换引用
_animateForward: after: panels[0].display="", panels[1].display="none"

11. 日志:返回

_animateBack: before: panels[0].display="", panels[1].display="none"
_animateBack: panels[1].display = ""
_animateBack: after set display: panels[0].display="", panels[1].display=""
_animateBack: 构建 panels[1] 内容
_animateBack: 动画开始
_animateBack: 动画结束,交换引用
_animateBack: after: panels[0].display="", panels[1].display="none"

12. 日志:关闭模型弹窗

(closeAllOverlays 只移除 visible 类,不碰 SlideMenu 内部状态)

13. 日志:再次打开模型弹窗(白屏!)

reset: before: panels[0].display="none", panels[1].display="none"

14. 构造函数:panels 初始化为空数组

typescript
constructor(container: HTMLElement, options?) {
    this.panels = [];
    // 创建两个 panel,push 进 panels
}

15. 构造函数:从容器中 querySelector 找面板

typescript
constructor(container: HTMLElement, options?) {
    this.container = container;
    this.inner = container.querySelector(".slide-inner")!;
    this.panels = Array.from(this.inner.querySelectorAll(".slide-panel")) as HTMLElement[];
    // ...
}

16. 查询:环境弹窗的 inner 里只有 2 个 panel

javascript
document.querySelectorAll("#sceneOverlay .slide-inner .slide-panel").length

返回值:2

17. 四个 SlideMenu 实例创建

typescript
export const modelSlideMenu = new SlideMenu(dom.modelPopup!, { ... });
export const motionSlideMenu = new SlideMenu(dom.motionPopup!, { ... });
export const sceneSlideMenu = new SlideMenu(dom.sceneOverlay!, { ... });
export const settingsSlideMenu = new SlideMenu(dom.settingsOverlay!, { ... });

18. #modelPopup 的 HTML 结构

html
<div id="modelPopup" class="overlay slide-menu">
    <div class="slide-inner">
        <div class="slide-panel"></div>
        <div class="slide-panel"></div>
    </div>
</div>

19. #sceneOverlay 的 HTML 结构

html
<div id="sceneOverlay" class="overlay slide-menu">
    <div class="slide-inner">
        <div class="slide-panel"></div>
        <div class="slide-panel"></div>
    </div>
</div>

20. 加 id 后测试:发现环境弹窗的 panels[1] 是模型的 panel

_animateForward after: panels[0].id = "env-panel-1", panels[1].id = "model-panel-0"

21. 查询:环境弹窗 inner 里竟有 4 个 panel

javascript
document.querySelectorAll("#sceneOverlay .slide-inner .slide-panel").length

返回值:4

22. 查询:模型弹窗 inner 里还是 2 个

javascript
document.querySelectorAll("#modelPopup .slide-inner .slide-panel").length

返回值:2

23. 构造函数:不够 2 个 panel 时补建

typescript
constructor(container: HTMLElement, options?) {
    this.container = container;
    this.inner = container.querySelector(".slide-inner")!;
    this.panels = Array.from(this.inner.querySelectorAll(".slide-panel")) as HTMLElement[];
    
    while (this.panels.length < 2) {
        const panel = document.createElement("div");
        panel.className = "slide-panel";
        this.inner.appendChild(panel);
        this.panels.push(panel);
    }
    // ...
}

24. 构造函数日志

typescript
console.log("constructor:", container.id, "panels found:", this.panels.length);

25. 初始化控制台输出

constructor: modelPopup panels found: 2
constructor: motionPopup panels found: 2
constructor: sceneOverlay panels found: 2
constructor: settingsOverlay panels found: 2

26. 调用栈:每次 showEnvMenu 都 new SlideMenu

new SlideMenu
showEnvMenu
showSceneMenu
onclick

27. showEnvMenu 每次 new SlideMenu

typescript
export async function showEnvMenu() {
    const m = await import("./env-menu");
    // ...
    const slideMenu = new SlideMenu(dom.sceneOverlay!, { ... });
    slideMenu.reset(buildEnvLevel());
}

28. showModelPopup 用单例

typescript
export function showModelPopup() {
    // ...
    modelSlideMenu.reset(buildLibraryLevel());
}

29. 构造函数:找到的 panel 不足时补建

typescript
this.panels = Array.from(this.inner.querySelectorAll(".slide-panel")) as HTMLElement[];

while (this.panels.length < 2) {
    // 创建新 panel
}

30. 构造函数日志:第一次打开环境弹窗

constructor: found 2 panels

31. 构造函数日志:第二次打开环境弹窗

constructor: found 4 panels

32. 最终修复:reset() 加一行

typescript
reset(level: PopupLevel): void {
    this.levels = [level];
    this.activeIdx = 0;
    this.transitioning = false;
    this.panels[0].style.display = "";  // ← 新增
    this.buildPanel(this.panels[0], level);
    this.panels[1].style.display = "none";
    this.inner.style.transform = "translateX(0)";
    this.inner.style.transition = "none";
    this.onAfterRender?.(level, this);
}