Files
ephron-ren-prd/prd-blog-toc-highlight-fix.md

162 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 博客目录高亮逻辑优化
> **版本**: 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. 当点击小标题H3H3 滚动到 `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)