freeCodeCamp Bootstrap Grid 实战:用 `row` 与 `col-xs-4` 让按钮并排显示
本篇基于 freeCodeCamp 开源课程中的 "Use the Bootstrap Grid to Put Elements Side By Side" 课程(对应挑战文件 bad88fee1348ce8acef08815.md),系统拆解 Bootstrap 响应式 12 列网格的运作原理,并结合本仓库中该挑战的真实种子代码、验收断言与参考实现,带你完整掌握 "容器 → 行 → 列" 三层结构与 col-*-* 宽度语法的用法。读完你既能独立完成 Cat Photo App 中 Like / Info / Delete 三个按钮的并排布局,也能把同一套网格思路迁移到任何响应式页面中。
一、课程定位:Cat Photo App 中的第三个操作按钮就位
在本仓库的课程结构中,这一挑战属于 Bootstrap 挑战块,完整顺序记录在 curriculum/structure/blocks/bootstrap.json 中。该块将 Bootstrap 3.3.7 的样式文件作为必选依赖引入:
"required": [
{
"link": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
}
]
同时块的 challengeOrder 标明本课(id: bad88fee1348ce8acef08815)紧跟在 "Warn Your Users of a Dangerous Action with btn-danger"(bad87fee1348ce8acef08814.md)之后,是 Cat Photo App 操作区打造的收尾环节:
- 先通过
btn-primary、btn-info、btn-danger为 Like / Info / Delete 三个按钮分别配上语义化颜色; - 本课再把这三个全宽按钮从竖直堆叠变为水平并排,形成一行动作栏;
- 后续课程再依次引入
span、Font Awesome 图标、表单控件等,逐步完成整个页面。
挑战前端的 challengeType: 0 表示这是一道 HTML/CSS 类课程,而其 Markdown 结构(frontmatter + --description-- + --hints-- + --seed-- + --solutions--)正是本仓库课程体系的标准模板——这种结构本身还受到 curriculum/schema/challenge-schema.js 的约束(其中 challengeType 被限定在 0~33 的整数范围),保证了每份挑战文件都能被课程引擎稳定解析与验证。
二、核心概念:响应式 12 列网格系统
课程描述开门见山地给出了本课的地基知识:
Bootstrap uses a responsive 12-column grid system, which makes it easy to put elements into rows and specify each element's relative width.
2.1 "12 列"意味着什么
Bootstrap 把一行(row)的可用宽度平均切分成 12 份逻辑列,然后通过 col-*-n 类告诉元素"你占据其中 n 份"。这样计算出的宽度是相对宽度(百分比),而非固定像素,因此天然能随视口缩放。
| 占位类 | 列数 | 相对宽度 |
|---|---|---|
col-xs-4 |
4 / 12 | 约 33.3%(1/3) |
col-xs-6 |
6 / 12 | 50%(1/2) |
col-xs-12 |
12 / 12 | 100%(整行) |
当同一行内各列占位之和恰好等于 12 时,整行被完整铺满、无需换行;若超过 12,超出的列会被换行到下一行(这是网格系统的通用行为,编写多组布局时可利用此特性)。
2.2 md、xs:针对不同屏幕宽度的列类
课程描述强调了一个关键点:Bootstrap 提供了多套列宽类,它们依据用户屏幕宽度被选用。手机屏幕窄、笔记本电脑屏幕宽,所以:
col-md-*:md表示 medium(中等),*是列数。它指定的是元素在**中等宽度屏幕(例如笔记本电脑)**上的列宽;col-xs-*:xs表示 extra small(超小),*是列数。它指定元素在**超小屏幕(例如老式小屏手机)**上的列宽。
按 Bootstrap 3 网格的分层使用规则,xs 是最基础的档位,col-xs-* 从超小屏幕起即生效;而 sm、md、lg 是逐级加宽的档位,只有当视口达到对应宽度时才启用、否则列会退回默认的纵向堆叠。Cat Photo App 之所以选用 col-xs-*,正是希望 Like / Info / Delete 三个按钮即使在手机上也能保持并排,而不是在窄屏下退化成竖排堆叠。课程描述中的完整解释对应原文:
Take for example Bootstrap's
col-md-*class. Here,mdmeans medium, and*is a number specifying how many columns wide the element should be. We'll usecol-xs-*, wherexsmeans extra small (like an extra-small mobile phone screen).
2.3 三层结构:"容器 → 行 → 列"
网格不能脱离上下文单独存在。把页面放入 container-fluid(来自前序课程 bad87fee1348bd9acde08712.md,提供全宽响应容器),内部再套 row 与 col-*-*,就构成了标准三层嵌套:
container-fluid ← 全宽响应容器
└── row ← 一行(由 12 列逻辑列组成)
└── col-xs-4 ← 占据 4/12 宽度
└── col-xs-4
└── col-xs-4
课程描述特意注明:"Most of Bootstrap's classes can be applied to a div element",并且 "The row class is applied to a div, and the buttons themselves can be nested within it." 也就是说,Bootstrap 网格不要求语义化标签,全部通过 div + class 组合即可完成布局,结构非常轻量。
三、任务拆解:让三个按钮各占 1/3 并排显示
本课的任务表述(原文)是:
Put the
Like,InfoandDeletebuttons side-by-side by nesting all three of them within one<div class="row">element, then each of them within a<div class="col-xs-4">element.
拆解为两条明确指令:
- 先建一行:新建一个
<div class="row">,把三个按钮整体包进去; - 再分三列:在每个按钮外面各包一层
<div class="col-xs-4">。
三个 col-xs-4 相加正好 4 + 4 + 4 = 12,铺满一行。每个按钮既不会互相挤压,也不会溢出换行,视觉上得到三等分的动作栏。
3.1 初始代码(种子文件)
挑战开始时,三个按钮是直接平铺在 container-fluid 下的(未包裹任何网格结构),因此默认按块级元素纵向堆叠。种子代码(保存于本挑战文件的 --seed-- 区块,完整摘录如下)还包含之前课程积累的自定义 CSS(如红色标题、圆形绿边框、缩小的图片):
<link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet" type="text/css">
<style>
.red-text {
color: red;
}
h2 {
font-family: Lobster, Monospace;
}
p {
font-size: 16px;
font-family: Monospace;
}
.thick-green-border {
border-color: green;
border-width: 10px;
border-style: solid;
border-radius: 50%;
}
.smaller-image {
width: 100px;
}
</style>
<div class="container-fluid">
<h2 class="red-text text-center">CatPhotoApp</h2>
<p>Click here for <a href="#">cat photos</a>.</p>
<a href="#"><img class="smaller-image thick-green-border" src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a>
<img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/running-cats.jpg" class="img-responsive" alt="Tuxedo cats running on dirt ground.">
<button class="btn btn-block btn-primary">Like</button>
<button class="btn btn-block btn-info">Info</button>
<button class="btn btn-block btn-danger">Delete</button>
<p>Things cats love:</p>
<ul>
<li>catnip</li>
<li>laser pointers</li>
<li>lasagna</li>
</ul>
<p>Top 3 things cats hate:</p>
<ol>
<li>flea treatment</li>
<li>thunder</li>
<li>other cats</li>
</ol>
<form action="https://freecatphotoapp.com/submit-cat-photo">
<label><input type="radio" name="indoor-outdoor"> Indoor</label>
<label><input type="radio" name="indoor-outdoor"> Outdoor</label>
<label><input type="checkbox" name="personality"> Loving</label>
<label><input type="checkbox" name="personality"> Lazy</label>
<label><input type="checkbox" name="personality"> Crazy</label>
<input type="text" placeholder="cat photo URL" required>
<button type="submit">Submit</button>
</form>
</div>
请留意:btn btn-block btn-primary / btn-info / btn-danger 这些类都必须原样保留——它们决定按钮是块级全宽、并带有 Like(蓝)/ Info(浅蓝)/ Delete(红)的语义配色;网格课程只负责摆放位置,不替换按钮自身样式。
四、验收标准解读:hints 究竟在测什么
挑战文件的 --hints-- 区块包含四条自动化断言,是理解题目边界的最佳材料。它们勾勒出"行、列、标签闭合"三个检查维度。
4.1 行内恰好有三个按钮
const row = document.querySelector('div.row');
const rowChildren = row?.querySelectorAll(`:scope ${'button'}`);
assert.lengthOf(rowChildren, 3);
从页面中找出第一个 div.row,断言其直接后代按钮恰好为 3 个——保证三个按钮确实被收进同一行,而不是被拆进多个行里。
4.2 每个 col-xs-4 恰好容纳一个按钮
const columns = document.querySelectorAll('div.col-xs-4');
assert.lengthOf(columns?.[0]?.querySelectorAll(`:scope ${'button'}`), 1);
assert.lengthOf(columns?.[1]?.querySelectorAll(`:scope ${'button'}`), 1);
assert.lengthOf(columns?.[2]?.querySelectorAll(`:scope ${'button'}`), 1);
按文档顺序取出所有 div.col-xs-4,逐一断言前三个列里各有且仅有一个按钮。这既要求存在三个独立列,也要求按钮被正确地一对一包裹(不允许一个列里塞两个按钮、另一个空着)。
4.3 标签成对闭合
assert.equal(code.match(/<\/button>/g).length, code.match(/<button/g).length);
assert.equal(code.match(/<\/div>/g).length, code.match(/<div/g).length);
最后两段通过对代码字符串统计 <button 与 </button>、<div 与 </div> 出现次数并要求相等,来强制标签闭合成对——漏写任何一个闭合标签都会导致计数不匹配而判错。这也是 freeCodeCamp 验证 HTML 结构完整性的常用手法:先断言"存在闭合标签",再断言"开闭数量相等"。
五、参考实现:完整的并排按钮方案
本挑战文件 --solutions-- 区块给出的标准答案是在三个按钮外加一层 row、再给每个按钮包一层 col-xs-4。相比种子代码,唯一的改动集中在图片后的按钮区域:
<div class="row">
<div class="col-xs-4">
<button class="btn btn-block btn-primary">Like</button>
</div>
<div class="col-xs-4">
<button class="btn btn-block btn-info">Info</button>
</div>
<div class="col-xs-4">
<button class="btn btn-block btn-danger">Delete</button>
</div>
</div>
将上面这段替换掉种子文件中三个"裸奔"的 <button>,即可通过全部 hints。完整可运行代码如下:
<link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet" type="text/css">
<style>
.red-text {
color: red;
}
h2 {
font-family: Lobster, Monospace;
}
p {
font-size: 16px;
font-family: Monospace;
}
.thick-green-border {
border-color: green;
border-width: 10px;
border-style: solid;
border-radius: 50%;
}
.smaller-image {
width: 100px;
}
</style>
<div class="container-fluid">
<h2 class="red-text text-center">CatPhotoApp</h2>
<p>Click here for <a href="#">cat photos</a>.</p>
<a href="#"><img class="smaller-image thick-green-border" src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a>
<img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/running-cats.jpg" class="img-responsive" alt="Tuxedo cats running on dirt ground.">
<div class="row">
<div class="col-xs-4">
<button class="btn btn-block btn-primary">Like</button>
</div>
<div class="col-xs-4">
<button class="btn btn-block btn-info">Info</button>
</div>
<div class="col-xs-4">
<button class="btn btn-block btn-danger">Delete</button>
</div>
</div>
<p>Things cats love:</p>
<ul>
<li>catnip</li>
<li>laser pointers</li>
<li>lasagna</li>
</ul>
<p>Top 3 things cats hate:</p>
<ol>
<li>flea treatment</li>
<li>thunder</li>
<li>other cats</li>
</ol>
<form action="https://freecatphotoapp.com/submit-cat-photo">
<label><input type="radio" name="indoor-outdoor"> Indoor</label>
<label><input type="radio" name="indoor-outdoor"> Outdoor</label>
<label><input type="checkbox" name="personality"> Loving</label>
<label><input type="checkbox" name="personality"> Lazy</label>
<label><input type="checkbox" name="personality"> Crazy</label>
<input type="text" placeholder="cat photo URL" required>
<button type="submit">Submit</button>
</form>
</div>
六、运行与自测:这条网格在仓库中如何被验证
这份挑战属于本仓库 legacy 课程体系中的 HTML/CSS 类任务,其验证逻辑就以 --hints-- 中的 JavaScript 断言为准。做题时你会经历两类检查:
- DOM 结构检查:在页面(iframe 沙箱)内用
document.querySelector等选择器核对.row、.col-xs-4与按钮的包含关系——这决定了"答案必须长成指定结构",单纯视觉上并排并不能得分; - 源码文本检查:对提交的 HTML 源码做正则计数,保证标签闭合配对。
这种"结构断言 + 文本断言"的双轨验证,也正是整个 curriculum/challenges 目录下大量 HTML/CSS 类挑战通用的验收模式。若想本地观察 Bootstrap 3.3.7 网格的真实渲染效果,可直接把 curriculum/structure/blocks/bootstrap.json 中声明的 Bootstrap 3.3.7 样式链接放入任意 HTML 页面,然后按上文结构书写 row / col-xs-4,拖动浏览器窗口即可看到三种按钮始终以三等分并排。
七、触类旁通:网格宽度是"相对宽度"的乘法表
本课不是网格在仓库课程中的孤例。往前看,"Split Your Bootstrap Row" 课程(bad87fee1348bd9aec908847.md)用两个 col-xs-6 把一行对半拆开;本课则用三个 col-xs-4 做三等分。两者叠加起来,就能组成一张实用的"12 列乘法表":
| 想要的分割 | 方案 | 列数合计 |
|---|---|---|
| 两等分(各 50%) | 两个 col-xs-6 |
6 + 6 = 12 |
| 三等分(各 ≈33.3%) | 三个 col-xs-4 |
4 × 3 = 12 |
| 四等分(各 25%) | 四个 col-xs-3 |
3 × 4 = 12 |
| 2:1 侧栏布局 | col-xs-8 + col-xs-4 |
8 + 4 = 12 |
理解"列宽 = 12 份中的若干份、且永远基于百分比"这一点后,你就能脱离记忆、直接推演出任意非对称布局的写法。若想深入这套布局在更大视口下的进阶用法,可继续阅读课程块 bootstrap.json 中 challengeOrder 所列的后续挑战(例如用 Font Awesome 图标点缀按钮、用 Bootstrap 对齐表单元素等),它们共同构成了一个从"响应式容器"到"栅格布局"再到"组件美化"的完整闭环。
小结
本课的核心收益可以浓缩为三句话:
- 三层结构:
container-fluid(容器)→row(行)→col-*-n(列); - 相对宽度:
col-*-n表示占 12 列中的 n 份,三个col-xs-4恰好铺满一行、按钮并排显示; - 档位语义:
xs(超小屏,手机)是最基础档位,md(中等屏,笔记本)等更大档位按屏幕宽度逐级启用。
把这三个概念连同本课的验收断言(行内恰有三个按钮、每列恰有一个按钮、标签成对闭合)落实到 Cat Photo App 的按钮区,你就完成了从"会写 HTML"到"能用栅格系统做响应式排版"的关键一跃。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00