diff --git a/docs/settings-sticky-overlap-decisions.md b/docs/settings-sticky-overlap-decisions.md new file mode 100644 index 0000000..17387bd --- /dev/null +++ b/docs/settings-sticky-overlap-decisions.md @@ -0,0 +1,97 @@ +# 设置页 Sticky 遮挡修复决策 + +本文锁定本轮“页面标题遮罩 + 卡片标题停靠”修复的最终实现策略。 + +## 1. 页面标题仍是最高层 sticky + +- 保留 `display()` 中的 `pageHeaderEl`。 +- 最终样式决策: + - `position: sticky` + - `top: 0px` + - `zIndex: 300` + - `backgroundColor: var(--modal-background, var(--background-primary))` +- 继续保留现有 inline style 方式,不新增全局样式表。 + +原因: + +- 当前仓库设置页布局主要仍由 inline style 负责。 +- 页面标题是统一的上层遮挡面,应该高于所有卡片标题。 + +## 2. 页面标题增加独立 mask,而不是只依赖 header 本体背景 + +- 在 `pageHeaderEl` 内增加: + - `data-settings-page-header-mask="true"` +- mask 使用: + - `position: absolute` + - 向上扩展 + - 向左右扩展 + - 与页面标题相同的不透明主题背景 +- mask 层级低于标题文本,但跟随页面标题一起处于最高层 sticky 堆叠上下文。 + +原因: + +- 仅靠 `pageHeaderEl` 自身背景,只能覆盖 header 盒子范围,无法盖住页面标题上方和左右的透明缺口。 + +## 3. 卡片标题恢复 sticky,但固定停在页面标题下方 + +- `settingsCardToggle` 最终样式决策: + - `position: sticky` + - `top: var(--ahs-settings-page-header-height, 64px)` + - `zIndex: 200` + - `backgroundColor: var(--modal-background, var(--background-primary))` +- 保留现有按钮视觉: + - `width: 100%` + - `border: 2px solid var(--background-modifier-border)` + - `fontSize: 1.5em` + - 点击切换折叠/展开 + +原因: + +- 卡片标题需要 sticky,才能满足“滚动到页面标题下方后停住”的交互。 +- `top` 必须基于页面标题真实高度,不能再写死为 `0`。 +- `zIndex` 必须低于页面标题层级,避免钻到标题前面。 + +## 4. 页面标题高度通过 CSS 变量驱动 + +- 在 `containerEl` 上写入: + - `--ahs-settings-page-header-height` +- 默认值: + - `64px` +- 运行时优先用 `ResizeObserver` 监听 `pageHeaderEl`。 +- 如可测量,则通过 `getBoundingClientRect()` 取真实高度并回写像素值。 +- 无法测量时保留 `64px` fallback。 + +原因: + +- 测试环境和部分渲染阶段无法可靠给出布局尺寸,需要一个稳定 fallback。 +- 真实 Obsidian 设置页中,标题高度可能受字体或缩放影响,运行时测量更稳妥。 + +## 5. `hide()` 负责 observer 清理 + +- 在 `hide()` 中断开页面标题高度 observer。 +- 同时清掉存储的 observer 引用,避免重复打开设置页时累积监听。 + +原因: + +- `displayInitialized` 会在 `hide()` 后复位,下一次打开会重建 DOM。 +- 如果不清理 observer,会造成重复观察和不可预测的高度回写。 + +## 6. 保持不变的边界 + +- 不改 `pageHeaderEl / settingsCardsContainer / cardEl / headerEl / bodyEl` 这一卡片 shell 结构。 +- 不改 `toggleCard()` 的展开/折叠逻辑。 +- 不改 `renderCard()` 的局部刷新边界。 +- 不改同步逻辑、设置数据结构、命令行为。 +- 不引入: + - scroll listener + - `IntersectionObserver` + - `requestAnimationFrame` 滚动逻辑 + +## 7. 与旧 sticky 文档的偏离说明 + +- 仓库里已有的旧文档把卡片标题视为 static,这是之前一次实现决策。 +- 本轮按新的用户合同执行,原因是当前真实 UI 目标已经变化: + - 页面标题需要继续 sticky 并补齐遮罩 + - 卡片标题也需要 sticky,但停靠在页面标题下方 + +因此,本轮不是延续旧方案,而是在同一真实代码接点上升级为“两层 sticky”结构。 \ No newline at end of file diff --git a/docs/settings-sticky-overlap-gap-report.md b/docs/settings-sticky-overlap-gap-report.md new file mode 100644 index 0000000..3603155 --- /dev/null +++ b/docs/settings-sticky-overlap-gap-report.md @@ -0,0 +1,97 @@ +# 设置页 Sticky 遮挡差距审计 + +本文基于当前仓库真实实现,定位设置页顶部透视与卡片标题停靠异常的实际接入点。 + +## 1. 当前 `display()` 已有页面级 sticky header,但遮罩不完整 + +- 入口在 [src/presentation/settings/PluginSettingTab.ts](src/presentation/settings/PluginSettingTab.ts)。 +- `display()` 首次渲染时已经创建了 `pageHeaderEl`,并设置了: + - `position: sticky` + - `top: 0` + - `zIndex: 100` + - `background: var(--background-primary)` +- 但当前 `pageHeaderEl` 只有自身盒子背景,没有任何向上、向左右扩展的遮罩层。 + +结果是:页面标题本身能 sticky,但上方和侧边留白区域仍可能透出下层设置内容,尤其在 Obsidian 设置容器滚动时更明显。 + +## 2. 当前卡片标题按钮是 static,不会停靠在页面标题下方 + +- `initializeCards()` 中五张卡片仍是统一 shell: + +```text +pageHeaderEl +settingsCardsContainer + cardEl + headerEl(button) + bodyEl(div) +``` + +- `settingsCardToggle` 当前只保留普通按钮样式: + - `display: flex` + - `width: 100%` + - `fontSize: 1.5em` + - `background: var(--background-primary)` + - `border: 2px solid var(--background-modifier-border)` +- 没有: + - `position: sticky` + - `top` + - `zIndex` + +结果是:卡片标题不会在滚动到页面顶栏下方时停住,因此无法实现“卡片标题栏顶到 Anki Heading Sync 下方后停止”的设计目标。 + +## 3. 当前没有页面标题高度变量,也没有测量逻辑 + +- `containerEl` 当前没有写入 `--ahs-settings-page-header-height`。 +- `PluginSettingTab` 当前没有: + - `ResizeObserver` + - `getBoundingClientRect()` 回退测量 + - 页面标题高度的状态同步 + +结果是:即使恢复卡片标题 sticky,也没有可靠的 `top` 偏移来源,卡片标题无法稳定停靠在页面标题栏下方。 + +## 4. 当前 `hide()` 不做页面标题测量清理 + +- `hide()` 当前只做: + - `displayInitialized = false` + - `cardShells.clear()` + - debounce 和缓存状态清理 +- 没有任何 observer 断开逻辑。 + +如果本轮引入页面标题高度观察器,必须在 `hide()` 中断开,避免设置页重复打开后残留多重监听。 + +## 5. 现有展开/折叠与局部刷新接点应保持不变 + +- `toggleCard()` 只维护 `expandedCardIds`。 +- `renderCard()` 只更新单卡: + - 标题文本 + - `aria-expanded` + - `bodyEl.style.display` + - `bodyEl.empty()` 后重绘 +- 已有测试也覆盖了: + - 折叠展开不整页重建 + - 卡片 1 局部刷新不整页重建 + +这部分已经是正确的局部刷新边界,本轮不应改动。 + +## 6. 父容器路径上未发现必须先处理的 sticky 阻断样式 + +- 当前拥有 sticky 行为的直接元素只有页面级 `pageHeaderEl`。 +- 在 `PluginSettingTab.ts` 的设置页装配路径中,未发现 `settingsCardsContainer`、`cardEl`、`bodyEl` 上存在会直接阻断标题按钮 sticky 的 `overflow: hidden/auto` 接入点。 + +因此本轮优先按用户合同落两层 sticky,并通过最小增量验证真实行为;不额外引入滚动监听或观察滚动位置的 JS 逻辑。 + +## 7. 结论 + +当前仓库与本轮目标之间的真实差距是: + +1. 页面级 sticky 标题栏已有,但缺少完整不透明遮罩。 +2. 卡片标题栏当前是 static,无法停靠在页面标题栏下方。 +3. 缺少页面标题高度 CSS 变量和测量更新逻辑。 +4. `hide()` 缺少 observer 清理。 + +最小且符合仓库现状的修复路径是: + +1. 保留 `pageHeaderEl` 作为最高层 sticky header,并补一层绝对定位 mask。 +2. 在 `containerEl` 上写入 `--ahs-settings-page-header-height`,默认 `64px`,运行时按真实高度更新。 +3. 让每个 `settingsCardToggle` 恢复 sticky,并使用页面标题高度变量作为 `top`。 +4. 在 `hide()` 中断开页面标题高度 observer。 \ No newline at end of file