Bootstrap

前言

  • 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"> <!-- 5.3 起可在这里切明暗 -->

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 必须,否则栅格在手机上按桌面宽度排版 -->
<title>Bootstrap 5 起步</title>

<!-- CSS:只有一个文件,不需要再单独引 reboot/grid -->
<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>

<!-- bundle 版已经包含 Popper,不需要单独引 -->
<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">                             <!-- 行间距(上下左右)设为 3 号刻度 -->
<div class="col-3">普通</div>
<div class="col-3 offset-3">向右偏移 3 列</div> <!-- margin-left: 25%,源码实测 -->
<div class="col-3 order-1">顺序 1</div> <!-- flex order 控制排列顺序 -->
</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
<!-- 声明式:只要 data-bs-* 就能用(前提是引了 bootstrap JS) -->
<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
// 原生 + Bootstrap 校验样式:needs-validation 这个类就是给这段脚本用的
(() => {
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; // 实测本机为 false
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; /* 影响 bg-primary/text-primary 等(部分类已预编译,改变量不一定全生效) */
--bs-border-radius: .5rem;
--bs-body-font-size: 1rem; /* 实测:body 计算值 16px,行高 --bs-body-line-height:1.5 -> 24px */
}

.my-brand {
background: var(--bs-primary);
}
  • 改 CSS 变量能调主题类,但像 .btn-primary 这类颜色在编译时已展开成具体值,只改变量不一定全变——要深度定制请用 Sass 变量重新编译:
1
2
3
4
5
// 自定义入口 custom.scss
$primary: #0066cc;
$enable-shadows: true;
$grid-breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px);
@import "bootstrap/scss/bootstrap"; // 或用 bootstrap/functions、mixins、utilities 只引需要的
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。

参考资料

本文结束 感谢您的阅读
正在加载今日诗词....