# 博客目录高亮逻辑优化 > **版本**: v1.0 > **日期**: 2026-05-06 > **状态**: ✅ 已修复 --- ## 一、问题描述 ### 1.1 现象 博客正文页右侧目录的高亮逻辑存在边界问题:当点击小标题(H3)时,页面正确滚动到该位置,但目录实际高亮的是上方的大标题(H2)。 ### 1.2 复现步骤 1. 访问博客文章(如 `/posts/hermes-chrome-opencode-ai-agent-bug`) 2. 找到一个小标题(H3)和它上方的大标题(H2)非常接近的章节 3. 点击目录中的小标题 4. 观察右侧目录的高亮状态 **预期行为**:小标题被高亮 **实际行为**:大标题被高亮 ### 1.3 影响范围 - 影响所有博客文章页 - 影响标题间距较小的章节 --- ## 二、根因分析 ### 2.1 原始代码 ```javascript function updateTocHighlight() { let current = ''; headings.forEach(heading => { const rect = heading.getBoundingClientRect(); if (rect.top <= 80) { current = heading.id; } }); } ``` ### 2.2 问题根因 1. 当点击小标题(H3)时,H3 滚动到 `rect.top = 80`(scroll-margin-top) 2. 由于滚动精度问题,H3 的 `rect.top` 可能是 `80.5` 或 `81` 3. 原逻辑使用 `<= 80` 判断,H3 不满足条件 4. 而上方的 H2 满足条件(`rect.top <= 80`) 5. 结果高亮选中了 H2 **核心问题**:阈值判断是硬性的,没有考虑滚动精度误差。 --- ## 三、解决方案 ### 3.1 方案对比 | 方案 | 实现 | 优点 | 缺点 | |------|------|------|------| | A. 扩大阈值 | `<= 82` | 简单 | 仍可能有边界问题 | | B. 距离最近算法 | 选择距离 80px 最近的标题 | 精确 | 稍复杂 | | C. 滚动后手动设置 | 点击时直接设置高亮 | 确定性高 | 需要额外逻辑 | ### 3.2 采用方案:距离最近算法 **原理**:遍历所有标题,找到距离目标位置(80px)最近的那个标题。 **实现**: ```javascript function updateTocHighlight() { const SCROLL_OFFSET = 80; let current = ''; let minDistance = Infinity; headings.forEach(heading => { const rect = heading.getBoundingClientRect(); // 只考虑在视口上方或接近顶部的标题 if (rect.top <= SCROLL_OFFSET + 20) { // 计算距离目标位置的绝对值 const distance = Math.abs(rect.top - SCROLL_OFFSET); // 选择距离最近的标题 if (distance < minDistance) { minDistance = distance; current = heading.id; } } }); } ``` **优点**: - 精确:选择距离目标位置最近的标题 - 鲁棒:不受滚动精度影响 - 直观:符合用户预期 --- ## 四、实现细节 ### 4.1 修改文件 - `blog/templates/post.html`:修改 `updateTocHighlight()` 函数 ### 4.2 算法说明 1. **目标位置**:`SCROLL_OFFSET = 80px`(导航栏高度 64px + 留白 16px) 2. **候选范围**:`rect.top <= SCROLL_OFFSET + 20`(即 <= 100px) 3. **选择标准**:`Math.abs(rect.top - SCROLL_OFFSET)` 最小的标题 ### 4.3 边界情况 - **标题在视口上方**(rect.top < 0):距离计算为 `|负数 - 80|`,值较大,不会被选中 - **标题恰好在 80px**:距离为 0,优先选中 - **标题在 80px 以下**:不满足 `<= 100` 条件,不参与计算 --- ## 五、测试验证 ### 5.1 测试用例 | 编号 | 测试步骤 | 预期结果 | |------|---------|---------| | T-001 | 点击 H3 标题(与 H2 间距 < 50px) | H3 被高亮 | | T-002 | 点击 H2 标题 | H2 被高亮 | | T-003 | 滚动页面(不点击) | 最接近顶部的标题被高亮 | | T-004 | 快速连续点击多个标题 | 每次点击后高亮正确 | ### 5.2 验证方法 1. 部署后访问博客文章 2. 找到 H2 和 H3 间距较小的章节 3. 点击目录中的 H3 4. 确认 H3 被高亮(而非 H2) --- ## 六、优先级与排期 | 优先级 | 任务 | 状态 | |--------|------|------| | P0 | 修改高亮算法 | ✅ 已完成 | | P1 | 测试验证 | 待部署后验证 | --- ## 附录 ### A. 相关文件 - `blog/templates/post.html`:目录生成和高亮逻辑 ### B. 参考资料 - [MDN: getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect)