前言
- Bootstrap 是一个「CSS + 组件 + JS 行为」三件套的前端框架:栅格 + 一组开箱即用的组件类 + 少量交互脚本。它的价值在于不用自己从零定样式,快速搭出能用、在手机上也还看得过去的页面;代价是页面容易「长得一样」,且引入的体积里有一大部分你用不到。
- 原笔记写的是 Bootstrap 4.5.2。本篇改按 Bootstrap 5.3.3 来讲(4.x 已停止维护),并专门给出一张 4 → 5 迁移对照表——因为存量项目里 BS4 的写法(
data-toggle、.ml-3、依赖 jQuery)非常常见。
- 本篇的实测方式:
- 下载 Bootstrap 5.3.3 的
dist/(bootstrap.min.css 约 232 KB,bootstrap.min.js 约 60 KB)与 Popper 2.11.8,在本机 Chrome 里加载后量出容器宽度、列宽、工具类的计算样式;
- 断点与容器尺寸同时从压缩后的 CSS 源码里核对(
540 / 720 / 960 / 1140 / 1320 五个 max-width 都能在文件里找到)。
- 一个诚实的限制:自动化标签页处于后台时浏览器会节流过渡动画,所以「Modal/Collapse 动画走完之后的状态」没能全部跑完(
backdrop 元素、body.modal-open 这类同步产生的结果是实测到的,动画结束态只按文档描述给出)。
引入方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!DOCTYPE html> <html lang="zh-cn" data-bs-theme="light">
<head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 5 起步</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet"> </head>
<body> <div class="container"> <h1 class="text-primary">Hello</h1> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </body>
</html>
|
| 文件 |
内容 |
什么时候用 |
css/bootstrap.min.css |
编译好的全套样式 |
常规使用 |
css/bootstrap.rtl.min.css |
从右往左(阿拉伯语等)版本 |
RTL 站点 |
js/bootstrap.bundle.min.js |
组件 JS + Popper |
需要下拉/气泡提示/悬浮提示时,最省事 |
js/bootstrap.min.js |
只有组件 JS |
自己已经引了 Popper(实测本机是分别引 popper.min.js + bootstrap.min.js 也能跑) |
js/bootstrap.esm.js |
ES 模块版 |
打包项目里按需 import { Modal } from 'bootstrap' |
scss/ |
Sass 源码 |
需要定制变量、只打包用到的组件时 |
- 生产环境建议把文件下到本地(
node_modules/bootstrap/dist/ 拷出来,或 npm 依赖),比挂在第三方 CDN 稳;引 CDN 时加上 integrity + crossorigin 防篡改。
- JS 放
</body> 前或用 defer;CSS 放 <head>。
栅格系统(重点)
Bootstrap 的栅格 = 容器 container + 12 列的行 row + 列 col,靠 Flexbox 实现,靠媒体查询在断点处换布局。
容器:实测与源码核对
| 容器类 |
行为 |
实测/源码 |
.container |
响应式定宽,随断点跳档、自动水平居中 |
本机视口 888px 时量得 720px(落在 md 档) |
.container-fluid |
始终 100% 宽 |
实测 888px(=视口宽) |
.container-sm / -md / -lg / -xl / -xxl |
从该断点起定宽,之前是 100% |
5.x 新增,写单页布局更省事 |
.container 在各断点的 max-width(源码里逐条核对到 540 / 720 / 960 / 1140 / 1320):
| 断点 |
媒体查询 |
.container 最大宽度 |
xs |
< 576px |
100%(100% 宽) |
sm |
≥ 576px |
540px |
md |
≥ 768px |
720px |
lg |
≥ 992px |
960px |
xl |
≥ 1200px |
1140px |
xxl |
≥ 1400px |
1320px |
列:12 等分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <div class="container"> <div class="row"> <div class="col-4">三分之一</div> <div class="col-4">三分之一</div> <div class="col-4">三分之一</div> </div> <div class="row"> <div class="col">自动等分</div> <div class="col">自动等分</div> </div> <div class="row"> <div class="col-md-6 col-lg-3">中等屏占一半,大屏占四分之一</div> <div class="col-md-6 col-lg-3">…</div> <div class="col-md-6 col-lg-3">…</div> <div class="col-md-6 col-lg-3">…</div> </div> </div>
|
- 实测
.col-4 的样式是 flex: 0 0 auto; width: 33.33333333%,在 720px 容器里量得 240px,正好三等分;.col-12 是 width: 100%。
.col(不带数字)= 等分剩余空间。
.col-auto = 按内容宽。
col-{bp}-{n} = 只在该断点起生效,小屏自动堆叠。
- 一行里列宽之和超过 12 会换行(不是被压缩);
.row-cols-* 可以强制每行几列。
间距(gutter)与偏移、排序
1 2 3 4 5
| <div class="row g-3"> <div class="col-3">普通</div> <div class="col-3 offset-3">向右偏移 3 列</div> <div class="col-3 order-1">顺序 1</div> </div>
|
.row 的默认沟槽来自 CSS 变量:实测计算值 --bs-gutter-x: 1.5rem、--bs-gutter-y: 0;行的左右 margin 是 calc(-.5 * var(--bs-gutter-x)),列的 padding 是半个沟槽——这就是「列之间看起来有间距但不会溢出容器」的原理。
- 间距刻度
0~5 对应 --bs-spacer 的倍数(g-3 ≈ 1rem),也可用 gx-*(横向)/ gy-*(纵向)分别控制,还能直接写在断点上:gx-md-4。
- 老写法
no-gutters 已删除,等价 .g-0。
什么时候不用栅格
Bootstrap 5.3 还提供了 CSS Grid 工具类(.grid、.g-col-*、.row-cols-*)与 .d-grid(如 d-grid gap-2 做按钮组间距)。若只是等分/间距,栅格够用;要做二维复杂对齐,直接用 CSS Grid 更直白(见《CSS入门》)。
常用组件
组件 = 一组固定类名(外观)+(可选)data-bs-* 属性或 JS API(行为)。下面是最常用的几个。
导航条 navbar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <nav class="navbar navbar-expand-lg bg-body-tertiary"> <div class="container-fluid"> <a class="navbar-brand" href="#">站点</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu" aria-controls="navMenu" aria-expanded="false" aria-label="展开导航"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navMenu"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">首页</a></li> <li class="nav-item"><a class="nav-link" href="#">关于</a></li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">更多</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="#">项一</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="#">项二</a></li> </ul> </li> </ul> <form class="d-flex" role="search"> <input class="form-control me-2" type="search" placeholder="搜索" aria-label="搜索"> <button class="btn btn-outline-success" type="submit">搜</button> </form> </div> </div> </nav>
|
navbar-expand-lg:小于 lg 折成汉堡菜单,大于等于 lg 横向展开——这是响应式导航的关键。
me-auto 把右侧内容顶到最右(BS4 里写 mr-auto)。
卡片、按钮、徽标、提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div class="card" style="width: 18rem;"> <img src="cover.jpg" class="card-img-top" alt="封面"> <div class="card-body"> <h5 class="card-title">标题 <span class="badge bg-primary">新</span></h5> <p class="card-text">一段说明文字。</p> <a href="#" class="btn btn-primary">主要</a> <a href="#" class="btn btn-outline-secondary">次要</a> </div> </div>
<div class="alert alert-success alert-dismissible fade show" role="alert"> 操作成功 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="关闭"></button> </div>
|
- 按钮语义色:
btn-primary / secondary / success / danger / warning / info / light / dark / link,加 outline- 变描线版。
- 尺寸
btn-sm / btn-lg。
- 块级用父容器
d-grid(BS5 删了 btn-block)。
alert-dismissible + .btn-close[data-bs-dismiss="alert"] 就是可关闭提示,不用写一行 JS。
- 实测样式:
.btn 是 inline-block + cursor: pointer。
.text-primary 计算色 rgb(13, 110, 253)。
.bg-light 是 rgb(248, 249, 250)。
.rounded-circle 是 border-radius: 50%。
弹窗 modal / 折叠 collapse / 离屏 offcanvas / 轻提示 toast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#myModal">打开弹窗</button>
<div class="modal fade" id="myModal" tabindex="-1" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"><h5 class="modal-title">标题</h5> <button type="button" class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body">内容</div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button> <button type="button" class="btn btn-primary">保存</button> </div> </div> </div> </div>
<button class="btn btn-link" data-bs-toggle="collapse" data-bs-target="#faq1" aria-expanded="false">展开答案</button> <div class="collapse" id="faq1"><div class="card card-body">答案内容</div></div>
|
编程式 API(全部挂在 bootstrap 命名空间下,实测 typeof bootstrap.Modal === 'function'):
1 2 3 4 5 6 7 8 9 10 11
| const modal = new bootstrap.Modal(document.getElementById('myModal'), { backdrop: 'static' }); modal.show(); modal.toggle(); modal.hide();
document.getElementById('myModal').addEventListener('shown.bs.modal', () => { document.querySelector('#myModal input')?.focus(); });
bootstrap.Collapse.getOrCreateInstance(document.getElementById('faq1')).show(); bootstrap.Tooltip.getOrCreateInstance(document.querySelector('[data-bs-toggle="tooltip"]')).show();
|
实测同步产生的结果:new bootstrap.Modal(el).show() 之后,document.querySelectorAll('.modal-backdrop').length === 1、document.body.className 里出现 modal-open。(show 类、aria-hidden 的变化发生在过渡动画结束回调里,后台标签页里过渡被节流,所以这一项我没能测到终态。)
组件的事件命名规律
所有 JS 组件都按这套事件名派发,{event} ∈ show / shown / hide / hidden / close / closed,命名空间固定 .bs.{组件}:
1 2
| show.bs.modal → shown.bs.modal → hide.bs.modal → hidden.bs.modal show.bs.collapse … close.bs.alert → closed.bs.alert
|
show/hide 阶段可 preventDefault() 拦下(例如未保存时不让关弹窗)。
其它值得记住的组件
| 组件 |
关键类 |
说明 |
| 轮播 |
.carousel .carousel-slide data-bs-ride="carousel" |
需要 .carousel-indicators、data-bs-slide-to |
| 手风琴 |
.accordion .accordion-item .accordion-button |
本质是带 data-bs-parent 的 collapse 组合 |
| 进度条 |
.progress + .progress-bar style="width:70%" |
条纹/动画加 .progress-bar-striped .progress-bar-animated |
| 列表组 |
.list-group .list-group-item .list-group-flush |
配合 d-flex justify-content-between 做侧栏 |
| 分页 |
.pagination .page-item.disabled |
aria-label 别忘了 |
| 表格 |
.table .table-striped .table-hover .table-sm .table-responsive |
窄屏要横向滚动就套 .table-responsive |
| 面包屑 |
.breadcrumb .breadcrumb-item.active |
aria-label="breadcrumb" |
| 悬浮/气泡 |
data-bs-toggle="tooltip" / popover |
必须手动初始化(见下的坑) |
表单组件
BS5 把表单类统一成 form-* 前缀(BS4 的 custom-control、custom-select、custom-file 都没了)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <form class="row g-3 needs-validation" novalidate> <div class="col-md-6"> <label for="uname" class="form-label">用户名</label> <input type="text" class="form-control" id="uname" required> <div class="valid-feedback">看起来不错</div> <div class="invalid-feedback">请填写用户名</div> </div> <div class="col-md-6"> <label for="city" class="form-label">城市</label> <select class="form-select" id="city"> <option value="">请选择</option><option value="1">郑州</option> </select> </div> <div class="col-12"> <label for="pw" class="form-label">密码</label> <div class="input-group"> <span class="input-group-text">🔒</span> <input type="password" class="form-control" id="pw" minlength="8"> </div> </div> <div class="col-12"> <div class="form-check"> <input class="form-check-input" type="checkbox" id="agree" required> <label class="form-check-label" for="agree">同意条款</label> </div> <div class="form-check form-switch"> <input class="form-check-input" type="checkbox" id="dark"> <label class="form-check-label" for="dark">深色模式</label> </div> </div> <div class="col-12"> <button class="btn btn-primary" type="submit">提交</button> </div> </form>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| (() => { const forms = document.querySelectorAll('.needs-validation'); Array.from(forms).forEach((form) => { form.addEventListener('submit', (e) => { if (!form.checkValidity()) { e.preventDefault(); e.stopPropagation(); } form.classList.add('was-validated'); }, false); }); })();
|
- 实测校验样式:
class="form-control is-invalid" 的元素 border-color 是 rgb(220, 53, 69),并且 padding-right: 36px(给右侧的反馈图标留位)。
- 手写校验态也可以:直接给控件加
is-invalid / is-valid,配合 .invalid-feedback / .valid-feedback。
- 其它:
.form-range(滑块)、.form-floating(浮动标签)、.form-text(说明文字)、.input-group-lg、.col-form-label(行内表单标签对齐)。
- 复选/单选的勾选状态判断仍然要用
prop('checked')(jQuery)或 .checked(原生)——Bootstrap 只管外观。
工具类( utility )
Bootstrap 大量「一个类一件事」的工具类,用它比自己写 CSS 快且不会跑偏:
| 类别 |
类名 |
实测/说明 |
| 间距 |
m-{0~5} auto、mt-、mb-、p-、ms-/me-/ps-/pe-(逻辑方向)、mx-/my-、断点版 p-md-3 |
--bs-spacer 为 1 单位,刻度 0~5 |
| 显示 |
d-none、d-block、d-inline、d-flex、d-grid、响应式 d-md-flex |
实测 .d-none → display: none |
| 弹性 |
flex-row/column、justify-content-*、align-items-*、flex-grow-0、gap-* |
见《CSS入门》 |
| 文字 |
text-start/center/end、text-lowercase、fw-bold、fst-italic、fs-1~6、text-truncate、lh-1/1_5、text-wrap/text-nowrap |
text-truncate 就是单行省略号三件套的成品 |
| 颜色 |
text-primary、bg-body-tertiary、border、border-top-0、text-body-secondary、link-success |
5.3 起推荐用 语义色(body、primary、secondary),暗色模式才会自动跟随 |
| 边框圆角 |
rounded、rounded-circle、rounded-pill、rounded-0、border-2 |
实测 .rounded-circle → border-radius: 50% |
| 阴影 |
shadow-none、shadow-sm、shadow、shadow-lg |
实测 .shadow 有 box-shadow 计算值 |
| 定位 |
position-relative、top-0、start-50、translate-middle、fixed-bottom、sticky-top |
start-50 translate-middle 是官方给的居中套路 |
| 尺寸 |
w-25/50/100、h-100、mw-100、ratio ratio-16x9 |
响应式媒体容器常用 ratio |
| 溢出/滚动 |
overflow-auto、overflow-hidden、.table-responsive |
— |
| 无障碍 |
visually-hidden、visually-hidden-focusable、text-decoration-none |
实测规则:width/height: 1px!important; margin:-1px!important; overflow:hidden (不是 display:none,读屏软件能读到) |
1 2
| <span class="visually-hidden">当前页面是第 2 页,共 5 页</span>
|
- 需要「只在大屏显示」就
d-none d-md-block;这类成对写法比媒体查询更省事。
- 用太多工具类会让 HTML 变难读,项目里仍建议把重复的模式提炼成自己的语义类(工具类只处理「微调」)。
深色模式与 CSS 变量定制
Bootstrap 5.3 的配色都走 CSS 变量,主题切换只要一个属性:
1
| <html data-bs-theme="dark">…</html>
|
1 2 3
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; document.documentElement.setAttribute('data-bs-theme', prefersDark ? 'dark' : 'light');
|
局部也生效:<div class="card" data-bs-theme="dark"> 只把这一块的配色翻黑(做「深色卡片 + 浅色页面」很好用)。
运行时改主色:
1 2 3 4 5 6 7 8 9
| :root { --bs-primary: #06c; --bs-border-radius: .5rem; --bs-body-font-size: 1rem; }
.my-brand { background: var(--bs-primary); }
|
- 改 CSS 变量能调主题类,但像
.btn-primary 这类颜色在编译时已展开成具体值,只改变量不一定全变——要深度定制请用 Sass 变量重新编译:
1 2 3 4 5
| $primary: #0066cc; $enable-shadows: true; $grid-breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px); @import "bootstrap/scss/bootstrap";
|
1
| sass custom.scss custom.min.css --style compressed
|
- 只要组件的一部分时,别 import 整个
bootstrap.scss,按需引 buttons、grid、modal 等模块,能把体积砍掉一大半。
Bootstrap 4 → 5 迁移对照
| 项目 |
BS4 |
BS5 |
| JS 依赖 |
jQuery 3.x |
无依赖,原生 DOM;需要 Popper 的下拉/提示用 bundle 版 |
| 数据属性 |
data-toggle / data-target |
data-bs-toggle / data-bs-target(实测 5.3 源码里 data-toggle 为 0 次) |
| 方向类 margin/padding |
.ml-* .mr-* .pl-* .pr-* |
.ms-* .me-* .ps-* .pe-*(逻辑属性,RTL 友好) |
| 断点 |
xs/sm/md/lg/xl |
增加 xxl(≥1400);容器最大宽 1320px |
| 浏览器支持 |
IE10/11 |
放弃 IE,用 CSS 自定义属性(IE 不支持) |
| 网格 |
.row-cols-* 有,沟槽用 no-gutters |
改 .g-0 / gx-* / gy-*,$grid-gutter-width: 1.5rem |
| 组件类 |
.custom-control .custom-select .custom-file .jumbotron .card-deck .media .badge-*-pill |
.form-check / .form-select / 去掉 jumbotron、card-deck、media;badge 配 rounded-pill |
| 颜色 |
固定浅色调 |
语义色 bg-body、text-body-secondary、border-light-subtle + 暗色模式 |
| JS 事件 |
shown.bs.modal 等 |
命名一致,但事件由原生 CustomEvent 派发,e.relatedTarget 等细节略不同;data-api 事件仍可用 document.addEventListener 捕获 |
| 图标 |
内置 glyphicon 早已删(要引 Font Awesome 或 BS Icons) |
Bootstrap Icons 独立库 |
| 工具类 |
.text-left .text-right |
.text-start .text-end |
- 升级顺序建议:先把
data-toggle → data-bs-toggle、ml/mr → ms/me 全局替换,再移除 jQuery 与 bootstrap4 的 JS,引 bundle,最后逐个回归弹窗/下拉/折叠。text-left 之类改名要检查 CSS 里的自定覆盖。
常见坑速查
- 手机上布局还是桌面版 → 漏了
<meta name="viewport" content="width=device-width, initial-scale=1">。
- Tooltip / Popover 不出现 → BS5 要求手动初始化:
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => new bootstrap.Tooltip(el))。
- 下拉菜单位置乱跑 / 不出现 → 引的是
bootstrap.min.js 但没引 Popper;改引 bootstrap.bundle.min.js。
- 弹窗背景出现但内容不显示 / 动画不结束 → 后台标签页里过渡被浏览器节流(本机实测遇到:
backdrop 与 body.modal-open 已产生,但 show 类要等过渡结束回调);把逻辑写在 shown.bs.modal 回调里而不是同步执行。
- 样式改不动 → 你的
<style> 在 Bootstrap 之前引入了,或被组件选择器权重压住;调整引入顺序,或提高自己选择器的权重(别急着上 !important)。
.row 里的列换行了 → 列宽相加超过 12;或者忘了外层必须是 .container/.container-fluid。
- 图标显示成方框 → 没引
bootstrap-icons.css,或引了但字体路径被打包工具改写。
- 老项目里
data-toggle 失效 → 版本已是 BS5,属性名要加 bs-(最典型的 4→5 症状)。
- 用
ml-auto 不生效 → 5.x 已改名 me-auto/ms-auto。
- 改
--bs-primary 主色不变 → 编译期展开的颜色改变量无效,需用 Sass 重新编译或在 CSS 里覆写 .btn-primary{--bs-btn-bg:…}(5.x 的按钮组件已支持按组件级变量覆写)。
- 整站长得像「Bootstrap 风」 → 只引需要的组件、覆写变量、加上自己的排版层;组件类用于结构和间距,品牌视觉自己定义。
练习
- 用
.container + .row 做一个「侧栏 3 列 + 正文 9 列」的两栏布局,要求在 md 以下自动堆叠成单列。
- 复刻一个后台页面:navbar(含搜索框与下拉用户菜单)+ 左侧
list-group + 右侧 card 表格(table-hover + table-responsive)+ 底部 pagination。
- 做一个可关闭的
alert 与一个 modal 确认框,全程只用 data-bs-*,不写一行 JS。
- 给一个表单加完整校验:
needs-validation + invalid-feedback,并检查提交时是否被拦住(对照《HTML入门》里「校验不通过时根本不发请求」的实测结论)。
- 把
data-bs-theme 做成一个开关,配合 matchMedia('(prefers-color-scheme: dark)') 记住用户选择(存 localStorage)。
- 从 Bootstrap 4 的页面(比如原笔记里的 4.5.2 示例)出发,做一次完整迁移:属性名、方向类、移除 jQuery、初始化 Tooltip。
参考资料