# 博客目录跳转被导航栏遮盖问题 > **版本**: v1.0 > **日期**: 2026-05-06 > **状态**: 📝 待评审 --- ## 一、问题描述 ### 1.1 现象 博客正文页(`/posts/{slug}`)右侧有目录(TOC),点击目录项可以跳转到对应章节。但跳转后,目标标题被顶部导航栏遮盖,用户需要手动向上滚动才能看到标题。 ### 1.2 复现步骤 1. 访问任意博客文章(如 `https://blog.ephron.ren/posts/hermes-chrome-opencode-ai-agent-bug`) 2. 滚动页面使右侧目录可见 3. 点击任意目录项(如「案例一:卡片显示不全」) 4. 观察页面跳转后的滚动位置 **预期行为**:标题完整可见,位于导航栏下方 **实际行为**:标题被导航栏遮盖,只能看到标题下半部分 ### 1.3 影响范围 - 影响所有博客文章页(`/posts/{slug}`) - 影响所有使用目录跳转的用户 - 不影响首页、归档页等无目录页面 --- ## 二、根因分析 ### 2.1 技术细节 **导航栏实现**(`blog/templates/base.html`): ```css .site-header { position: fixed; top: 0; left: 0; right: 0; z-index: 100; min-height: 64px; } body { padding-top: 64px; /* 补偿导航栏高度 */ } ``` **目录实现**(`blog/templates/post.html`): ```javascript // 生成目录链接 a.href = '#' + id; // 如 #heading-0 // 目录高亮判断 if (rect.top <= 120) { current = heading.id; } ``` ### 2.2 问题根因 当点击 `href="#heading-0"` 的链接时,浏览器会执行以下操作: 1. 找到 `id="heading-0"` 的元素 2. 将该元素滚动到视口顶部(`scrollIntoView` 的默认行为) 3. 由于导航栏是 `position: fixed`,它始终在视口顶部 4. 标题被滚动到视口顶部后,立即被 fixed 导航栏覆盖 `body { padding-top: 64px }` 只对页面初始加载有效,对锚点跳转无效。 ### 2.3 为什么目录高亮用了 120px? ```javascript if (rect.top <= 120) { current = heading.id; } ``` 这里的 120px 是经验值(64px 导航栏 + 一些留白),用于判断标题是否「接近顶部」。但这只是高亮逻辑,不影响实际滚动位置。 --- ## 三、解决方案 ### 3.1 方案对比 | 方案 | 实现复杂度 | 兼容性 | 用户体验 | 推荐度 | |------|-----------|--------|----------|--------| | A. CSS `scroll-margin-top` | ⭐ 低 | 现代浏览器 | ⭐⭐⭐ 最佳 | ✅ 推荐 | | B. JS `scrollIntoView` | ⭐⭐ 中 | 全部 | ⭐⭐ 良好 | 备选 | | C. JS `scrollTo` + offset | ⭐⭐ 中 | 全部 | ⭐⭐ 良好 | 备选 | ### 3.2 推荐方案:CSS `scroll-margin-top` **原理**:CSS `scroll-margin-top` 属性可以为元素设置滚动外边距,当该元素被滚动到视口时,会自动添加额外的顶部间距。 **实现**: ```css /* 为所有标题添加滚动外边距 */ .post-content h1, .post-content h2, .post-content h3, .post-content h4, .post-content h5, .post-content h6 { scroll-margin-top: 80px; /* 64px 导航栏 + 16px 留白 */ } ``` **优点**: - 纯 CSS 实现,无需 JavaScript - 浏览器原生支持,性能最佳 - 自动应用于所有锚点跳转(包括浏览器地址栏直接输入) - 不影响现有 JS 逻辑 **兼容性**: - Chrome 61+ ✅ - Firefox 68+ ✅ - Safari 14.1+ ✅ - Edge 79+ ✅ ### 3.3 备选方案:JS `scrollIntoView` 如果需要支持旧版浏览器,可以用 JS 拦截点击事件: ```javascript // 拦截 TOC 链接点击 tocList.addEventListener('click', function(e) { const link = e.target.closest('a'); if (!link) return; e.preventDefault(); const targetId = link.getAttribute('href').substring(1); const target = document.getElementById(targetId); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); // 手动添加偏移 window.scrollBy(0, -80); } }); ``` **缺点**: - 需要处理 `scrollBy` 的时序问题(`scrollIntoView` 是异步的) - 可能出现闪烁(先跳到目标位置,再偏移) - 不影响地址栏直接输入锚点的情况 --- ## 四、实现细节 ### 4.1 修改文件 - `blog/templates/post.html`:在 `{% block extra_styles %}` 中添加 CSS ### 4.2 具体改动 在 `.post-content h4 { ... }` 之后添加: ```css /* 修复锚点跳转被导航栏遮盖 */ .post-content h1, .post-content h2, .post-content h3, .post-content h4, .post-content h5, .post-content h6 { scroll-margin-top: 80px; } ``` ### 4.3 偏移量计算 ``` 导航栏高度: 64px 额外留白: 16px 总计: 80px ``` 如果未来导航栏高度变化,需要同步修改这个值。建议将其提取为 CSS 变量: ```css :root { --nav-height: 64px; --scroll-offset: 80px; /* nav-height + 16px */ } .post-content h1, .post-content h2, .post-content h3, .post-content h4, .post-content h5, .post-content h6 { scroll-margin-top: var(--scroll-offset); } ``` ### 4.4 目录高亮逻辑调整 当前高亮逻辑使用 `rect.top <= 120`,这个值应该与 `scroll-margin-top` 保持一致: ```javascript // 当前 if (rect.top <= 120) { // 建议改为 if (rect.top <= 80) { ``` 或者提取为变量: ```javascript const SCROLL_OFFSET = 80; // 与 CSS --scroll-offset 保持一致 function updateTocHighlight() { let current = ''; headings.forEach(heading => { const rect = heading.getBoundingClientRect(); if (rect.top <= SCROLL_OFFSET) { current = heading.id; } }); // ... } ``` --- ## 五、测试验证 ### 5.1 测试用例 | 编号 | 测试步骤 | 预期结果 | |------|---------|---------| | T-001 | 点击目录中的 H2 标题 | 标题完整可见,位于导航栏下方 | | T-002 | 点击目录中的 H3 标题 | 标题完整可见,位于导航栏下方 | | T-003 | 浏览器地址栏输入 `#heading-0` | 标题完整可见 | | T-004 | 快速连续点击多个目录项 | 无闪烁,每次跳转位置正确 | | T-005 | 移动端视口下点击目录 | 行为一致(目录在移动端隐藏,此项可跳过) | ### 5.2 验证方法 1. 部署后访问博客文章 2. 打开浏览器开发者工具 3. 点击目录项,观察 `scroll-margin-top` 是否生效 4. 检查标题是否位于导航栏下方 --- ## 六、风险与注意事项 ### 6.1 风险 - **无**:`scroll-margin-top` 是纯 CSS 属性,不影响现有布局和交互 - **兼容性**:仅影响旧版浏览器(<2020 年),可忽略 ### 6.2 注意事项 - 如果未来导航栏高度变化,需要同步修改 `--scroll-offset` 变量 - 目录高亮逻辑的阈值应与 `scroll-margin-top` 保持一致 --- ## 七、优先级与排期 | 优先级 | 任务 | 预估时间 | |--------|------|---------| | P0 | 添加 `scroll-margin-top` CSS | 5 分钟 | | P1 | 提取 CSS 变量 | 5 分钟 | | P2 | 调整目录高亮逻辑阈值 | 5 分钟 | **总计**:15 分钟 --- ## 附录 ### A. 相关文件 - `blog/templates/base.html`:导航栏定义 - `blog/templates/post.html`:目录生成和高亮逻辑 ### B. 参考资料 - [MDN: scroll-margin-top](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin-top) - [CSS Scroll Snapping](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap)