原生css可实现水平滚动,但需绕开bootstrap的flex-wrap:wrap和父级flex截断滚动上下文两大坑:.nav必须显式设flex-wrap:nowrap,且需用独立容器(如.scrollable-nav)包裹并设overflow-x:auto、white-space:nowrap等,避免直接作用于.modal-body或.nav本身。

原生 CSS 就能搞定,不需要 JS 或第三方库;但必须绕开 Bootstrap 默认的 flex-wrap: wrap 和父级 flex 截断滚动上下文这两个坑。
为什么直接加 overflow-x: auto 没用
因为 Bootstrap 5 的 .nav 默认带 flex-wrap: wrap,项目一撑不开就换行,根本不会触发水平溢出;更隐蔽的是,若导航放在 .modal-body 或 .navbar 内,其父级(如 .modal-dialog)是 display: flex,会直接截断子元素的滚动能力——哪怕你给 .nav 加了 overflow-x: auto,浏览器也无视。
- 临时验证:给容器加
border: 1px solid red,看实际渲染宽度是否超出父级 - 检查 computed style,确认
flex-wrap确实是nowrap,不是继承或未覆盖 - 移动端 Safari 下,
overflow-x: auto可能无惯性,需补-webkit-overflow-scrolling: touch
.nav 容器必须显式设 flex-wrap: nowrap
Bootstrap 的 .nav 类默认包含 flex-wrap: wrap,这是水平滚动失效的最常见原因。不重写它,其他样式全白搭。
- 推荐写法:
.nav.scrollable-nav { display: flex; flex-wrap: nowrap; overflow-x: auto; width: 100%; } -
width: 100%不可省——没有明确宽度,overflow-x在 flex 容器里可能不生效 - 为每个
li加flex-shrink: 0或white-space: nowrap,防止文字或子项内部压缩换行 - 避免用
.nav-pills做滚动导航——它带圆角、背景色和 hover 状态,语义不符,且 padding 可能干扰滚动体验
Modal 里滚动导航必须用独立 wrapper
在 .modal 中,.modal-dialog 是 display: flex,.modal-body 是它的 flex 子项,无法承载滚动上下文。你不能给 .modal-body 加 overflow-x,而要包一层新容器。
- 结构必须是:
<div class="modal-body"><div class="scrollable-nav"><ul class="nav">...</ul></div></div> -
.scrollable-nav要设:display: block; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; -
.scrollable-nav不能是.nav的父级 flex 子项(比如别让它直接在.modal-dialog里),否则仍被截断 - 如果用 iScroll 替代原生滚动,目标容器必须是
.scrollable-nav,不是.nav本身
移动端划不动?检查 touch-action 和事件拦截
原生滚动在 iOS Safari 或某些 Android 浏览器上容易卡顿、无惯性、第一次拖拽没响应,本质是 touch 事件被默认行为(页面滚动)劫持了。
- 给滚动容器加
touch-action: pan-x,明确告诉浏览器“只允许水平拖拽” - 若仍不灵,监听
touchstart+touchmove,并在touchmove中调e.preventDefault()(注意阈值判断,避免误杀点击) - iScroll 初始化时务必设
{ scrollX: true, scrollY: false, preventDefault: false },否则 touch 会被拦截 - 别在
$(document).ready()里初始化 iScroll——Modal 动态插入时,得等shown.bs.modal事件后再调
真正卡住人的从来不是怎么写 CSS,而是搞不清哪个父级在悄悄截断滚动流;每次加完 overflow-x: auto 却没反应,先查两件事:父容器是不是 flex,.nav 的 flex-wrap 到底有没有被覆盖掉。











