可用 offcanvas + sticky-top 实现:小屏用 offcanvas-start 折叠目录,大屏用 sticky-top 固定左侧目录;需设 min-height: 100vh、右侧 overflow-y: auto 与 scrollspy 配合锚点高亮,注意 offset 和容器 position:relative。
用 bootstrap 5 的 offcanvas + sticky-top 实现左侧固定目录
bootstrap 5 原生不提供“左侧固定 toc + 右侧滚动内容”的开箱即用组件,但靠 offcanvas(用于小屏折叠)和 sticky-top(用于大屏固定)组合就能稳住布局。关键不是套模板,而是控制两个区域的流式行为与 z-index 层级。
常见错误是直接给 nav 加 position: fixed,结果右侧内容被遮挡、滚动错位,或者小屏下目录消失后无法唤出。
- 左侧目录容器必须设
min-height: 100vh,否则在内容短时会塌缩 -
sticky-top只在父容器有滚动时才生效,所以右侧内容区要加overflow-y: auto并设高度限制(比如max-height: calc(100vh - 60px)) - 小屏下用
offcanvas-start替代固定,触发按钮需放在navbar或顶部工具栏里,避免被 sticky 元素盖住 - 别忘了给
offcanvas内部的nav加flex-column,否则链接横向堆叠
目录项如何自动高亮当前锚点(scrollspy 配置要点)
scrollspy 是 Bootstrap 提供的滚动监听方案,但它对容器结构很敏感:必须指定正确的 target(目录容器)、offset(常因 fixed navbar 而偏移),且所有锚点目标(id)必须存在于文档流中——不能是 display: none 或 visibility: hidden 的元素。
典型失败现象:滚动后无任何 item 高亮,或高亮滞后/跳变。
- 确保目标内容区有
data-bs-spy="scroll",且父容器有position: relative(否则 offset 计算失准) -
offset值通常设为导航栏高度,比如data-bs-offset="60"(对应.navbar高 60px) - 目录中的链接必须形如
@#@#@#@#@#@#@#@#@#@0,对应内容块为<div id="section-1">...</div> - 如果内容动态加载,需手动调用
bootstrap.ScrollSpy.getInstance(el).refresh()
响应式断点怎么切:何时固定、何时收进 offcanvas
Bootstrap 的 col-lg-3 + col-lg-9 在 lg(≥992px)以上才分栏,但固定目录只应在 ≥1200px(xl)才启用,否则中屏(992–1199px)下左右挤在一起,阅读体验反而差。这不是“适配”,是“取舍”。
容易忽略的是:offcanvas 的显示/隐藏逻辑要和 CSS 栅格联动,否则小屏下目录既收不进侧边栏,又没被隐藏。
- 左侧目录默认加
class="d-none d-xl-block",只在 xl+ 显示并固定 - offcanvas 触发按钮加
class="d-xl-none",只在 xl 以下显示 - offcanvas 本身用
class="offcanvas offcanvas-start d-xl-none",确保它不会在大屏意外出现 - 不要依赖 JS 切换 class 控制显隐——CSS 媒体查询更可靠、无闪动
为什么不用 position: sticky 直接套在 nav 上?
可以套,但会出问题:sticky 的“粘性边界”是最近的具有滚动机制的祖先,而 Bootstrap 的 .container 或 .row 默认不滚动。一旦你给外层加 overflow,又可能截断下拉菜单、tooltip 等浮层——因为它们被限制在滚动容器内了。
更实际的问题是:sticky 导航在页面顶部有 navbar 时,会和 navbar 重叠;而 sticky-top 类内部已处理了 top: 0 和 z-index: 1020,和 Bootstrap 的 navbar(z-index: 1030)天然协同。
- 坚持用
sticky-top,别自己写position: sticky - 如果目录需要悬停在 navbar 下方,就给它加
top: 60px(配合z-index避免被盖) - 测试时务必打开浏览器 DevTools 的“Toggle device toolbar”,手动拖拽宽度到 1199px、1200px 边界,看切换是否干净
最麻烦的其实是锚点滚动平滑度和 history pushState 的同步——Bootstrap 的 scrollspy 不管这个,得自己用 scrollIntoView({ behavior: 'smooth' }) 或监听 click 补一层逻辑。这点没人提,但用户一滚就跳,体验直接打折。











