init: consolidate all ephron.ren PRDs and docs
This commit is contained in:
161
prd-blog-toc-highlight-fix.md
Normal file
161
prd-blog-toc-highlight-fix.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# 博客目录高亮逻辑优化
|
||||
|
||||
> **版本**: 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)
|
||||
Reference in New Issue
Block a user