必须用 offcanvas 实现移动端侧边栏,因 d-none 仅隐藏 dom 而非移出文档流,会导致焦点跳过、屏幕阅读器误读、滚动错乱及遮罩失效;桌面端需 d-flex + flex-shrink-0 + flex-grow-1 并约束高度,移动端必须独立 offcanvas 结构。

直接用 d-flex + flex-shrink-0 + flex-grow-1 就能搭出基础弹性侧边栏,但真要响应式,必须拆成两套 DOM 结构:桌面端保留弹性布局,移动端必须用 offcanvas 完全移出文档流——只靠 d-none d-md-block 会留 DOM 占位、键盘焦点跳过、屏幕阅读器误读。
为什么不能只靠 d-flex 和媒体查询显隐
常见错误是给侧边栏加 d-none d-md-block,以为小屏就“没了”。实际它仍在渲染、仍占空间、仍可被 Tab 键聚焦。典型问题包括:
- 主内容区滚动错乱(隐藏的侧边栏拉高了
.row高度) -
offcanvas展开后背景遮罩不显示(d-none干扰了aria-hidden切换) - 双指缩放时意外触发横向滚动(隐藏列仍参与 layout flow)
- 屏幕阅读器持续朗读菜单项,违反 WCAG 要求
桌面端用 d-flex 布局但必须加约束条件
弹性布局本身没问题,但 Bootstrap 默认结构容易让 flex-shrink-0 失效或高度塌缩。关键控制点:
- 外层容器必须用
d-flex flex-column或d-flex(非.row),避免.row的overflow: hidden截断 sticky 上下文 - 侧边栏内部容器加
flex-shrink-0+ 显式宽度(如style="width: 250px;"),否则浏览器可能压缩它 - 主内容区必须用
flex-grow-1,且父容器需有明确高度(如style="height: calc(100vh - 64px);") - 若需滚动固定,侧边栏内层加
position-sticky top-0 h-100 overflow-y-auto,别指望父.col自动撑高
移动端必须用 offcanvas 替代显隐类
这是唯一符合语义和无障碍要求的做法。结构上不能复用桌面端的 .col,而要另起一套:
- 桌面端结构:
<div class="col-md-3">...</div> <div class="col-md-9">...</div> - 移动端结构:
<div class="d-lg-none"><div class="offcanvas offcanvas-start" id="sidebar">...</div></div> -
offcanvas宽度只能用 CSS 变量定制:style="--bs-offcanvas-width: 280px;",改width或max-width会破坏 transform 定位 - 触发按钮放在主内容区顶部,
data-bs-target必须和offcanvas的id完全一致(大小写敏感) - 主内容区加
style="min-height: 100vh;",否则offcanvas展开后页面塌陷
offcanvas 容器里千万别加自定义定位类
很多开发者给 offcanvas 元素额外加 class="sidebar" 或 position: relative,这会直接覆盖 Bootstrap 的 transform 渲染逻辑:
- 关闭按钮必须是
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>,手写 × 或用onclick会丢掉 ESC 关闭和屏幕阅读器支持 - 别在
offcanvas上设float、width、position: relative—— 这些属性和 Bootstrap 的 transform 冲突,导致错位或遮罩不显示 - 搜索框等交互元素必须塞进
<div class="offcanvas-body">,不在里面的话,小屏下根本不会渲染(不是隐藏,是 DOM 没生成)<li>多级折叠菜单在 <code>offcanvas中默认会连带展开,必须用 JS 在show.bs.offcanvas事件里手动收起所有.collapse.show
真正难的不是写对类名,而是理解:桌面端的 flex-shrink-0 和移动端的 offcanvas 不是同一套机制的两种写法,它们服务于完全不同的渲染上下文——前者依赖弹性容器的高度约束,后者依赖 transform 移出文档流。混用或降级处理,几乎必然踩进可访问性和滚动行为的坑里。











