freeCodeCamp 栅格实战:用 Bootstrap 的 col-xs-6 让 Radio Buttons 自适应布局
本指南围绕 freeCodeCamp 开源仓库中 Bootstrap 模块的经典关卡 Responsively Style Radio Buttons 展开,讲解如何把「12 列栅格系统」应用到表单元素上:将一对单选框分别放进 col-xs-6 列,使它们在任意屏幕宽度下都平分一整行、均匀分布。读完本文,你将掌握 row + col-*-* 栅格对表单控件的套用方式、freeCodeCamp 关卡文档(frontmatter + 描述 + 测试 + 种子代码 + 参考答案)的结构,以及通过仓库中的关卡源码、结构 JSON 与相邻关卡串起的前后知识链。
一、这一关要解决什么问题
关卡原文描述(对应 curriculum/challenges/english/blocks/bootstrap/bad87fee1348bd9aedb08845.md 第 13-19 行)写得很直白:
You can use Bootstrap's
col-xs-*classes onformelements, too! This way, our radio buttons will be evenly spread out across the page, regardless of how wide the screen resolution is.
也就是说,Bootstrap 栅格类不仅能用在图片、按钮上,同样适用于 form 表单里的元素。如果把一对单选按钮各自放进一列栅格,它们就能在任意屏幕宽度下都平分一行、保持均匀间距,而不是挤在表单左侧。
关卡任务拆解
- 把两个
radio单选按钮一起嵌套进一个<div class="row">; - 再把每个单选按钮分别嵌套进一个
<div class="col-xs-6">。
关卡特意提示:单选按钮本质上是 type="radio" 的 input 元素。这是后面理解测试断言、理解「栅格加在包裹层而非 input 本身」的关键。
二、为什么 col-xs-6 能做到「无论分辨率多宽都均匀分布」
freeCodeCamp 这一 Bootstrap 模块固定在 Bootstrap 3 之上。仓库的模块定义文件 curriculum/structure/blocks/bootstrap.json 第 5-8 行的 required 字段明确声明了版本:
"required": [
{
"link": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
}
]
即该模块所有关卡运行在 Bootstrap 3.3.7 的 CSS 之上。理解下面三点,就理解了本关的「为什么」:
- 栅格一共 12 列:
col-xs-6表示占据 6 / 12 = 50% 的宽度。两个col-xs-6恰好填满一行 12 列。 - col-xs- 是"永远生效"的最小屏类*:Bootstrap 3 是移动优先设计,
col-xs-*不依赖任何媒体查询断点,从最小屏到最大屏都按百分比宽度生效;而col-sm-*、col-md-*、col-lg-*分别要到更宽的视口才生效。因此这里选col-xs-6,才能做到原文档强调的 "regardless of how wide the screen resolution is"。 - 栅格是"布局容器"而非"控件样式":栅格类改变的是元素的盒模型与浮动布局,不影响单选框本身的圆形小按钮外观。所以正确做法是给单选框外面的
<div>加栅格类,而不是直接给<input>加——直接对input使用栅格类既不符合 Bootstrap 惯例,也无法被本关测试通过。
同模块更早的关卡 Use the Bootstrap Grid to Put Elements Side By Side 已经演示过 8 + 4、4 + 4 + 4 的切分;本关则是把同样的 12 列数学搬到 form 内部。
三、栅格类可以作用在任意元素上的通用结论
关卡文档用一个非常清晰的表达给出了可推广的结论:col-xs-* classes are applicable to all form elements(适用于所有表单元素)。这意味着同一种写法可以迁移到:
| 表单控件 | 12 列切分 | 说明 |
|---|---|---|
| 单选按钮(2 个) | 每个 col-xs-6 |
6+6=12,两个选项各占半行 |
| 复选框(3 个) | 每个 col-xs-4 |
4+4+4=12,三个选项横排一列 |
| 文本输入框 | 任意 1-12 的列宽 | 与后续 "Line up Form Elements" 关卡一致 |
紧跟在后的兄弟关卡 Responsively Style Checkboxes(bad87fee1348bd9aeda08845.md)正是这一通用结论的下一站:把三个复选框各自放进 col-xs-4,4+4+4=12 又一次完美凑满一行。两关连起来学,能彻底理解「栅格类 = 表单布局工具」。
四、仓库视角:关卡在课程结构中的位置
本关是 Bootstrap 模块中的一环。观察两处仓库结构文件可以还原它的"坐标":
- 模块清单 curriculum/structure/blocks/bootstrap.json 第 10-135 行的
challengeOrder数组按顺序列出整个 Bootstrap 模块的挑战,本关(id: bad87fee1348bd9aedb08845,标题 "Responsively Style Radio Buttons")排在第 15 位,紧接着的前后关卡分别是 "Add Font Awesome Icons to all of our Buttons" 和 "Responsively Style Checkboxes"。该文件还注明helpCategory: "HTML-CSS"与blockLayout: "legacy-challenge-list",说明这是一个经典(legacy)HTML/CSS 动手练习模块。 - 上层课程文件 curriculum/structure/superblocks/front-end-development-libraries.json 把
bootstrap列在 Front End Development Libraries(前端开发库)超级块之下,与 jquery、sass、react、redux 等并列。
也就是说,这段讲解栅格的 markdown 不是孤立的教程片段,而是被 challengeOrder 精确编排、会被课程构建流水线消费的真实课程内容。挑战文件的 YAML frontmatter(第 1-11 行)提供了完整元数据:
| 字段 | 本关取值 | 含义 |
|---|---|---|
id |
bad87fee1348bd9aedb08845 |
全局唯一挑战 ID,被 bootstrap.json 的 challengeOrder 引用 |
title |
Responsively Style Radio Buttons | 挑战标题 |
challengeType |
0 |
挑战类型编号 |
forumTopicId |
18270 |
关联论坛话题 |
dashedName |
responsively-style-radio-buttons | URL 友好名称 |
required |
Font Awesome 4.2.0 CSS | 页面额外注入的依赖,raw: true 表示以原始标签引入 |
这些字段与 --description--、--hints--、--seed--、--solutions-- 区块共同构成 freeCodeCamp 挑战文件的固定格式,仓库中的 curriculum/schema 目录下提供了配套的 schema 校验器与快照测试来保证这类文件格式合规。
五、自动化测试:这一关是如何被"裁判"验证的
原文档的 --hints-- 段(第 21-43 行)是三条会在浏览器里执行的自动化断言,理解它们等于理解了本关的全部验收标准。它们用 DOM 查询来检查你的 HTML 结构:
断言 1:所有 radio 必须在一个 .row 容器里
const radioButtons = document.querySelectorAll(`div.row input[type=radio]`);
assert.lengthOf(radioButtons, 2);
div.row input[type=radio] 选择所有「位于 class 为 row 的 div 内部、type 为 radio 的 input」,数量必须恰好为 2。这说明文档描述中"嵌套进 row"里的"嵌套",对测试而言等价于成为 row 的后代节点。
断言 2:每个 radio 要各归其位,处在自己的 .col-xs-6 列里
const radioButtons = document.querySelectorAll(`div.col-xs-6 input[type=radio]`);
assert.lengthOf(radioButtons, 2);
要求两个 radio 必须分别位于各自 col-xs-6 的 div 内(成为其内部元素),共 2 个命中。注意:把两个 radio 塞进同一个 col-xs-6 div 只有 1 次命中,同样无法通过。
断言 3:所有 div 标签必须成对闭合
assert.match(code, /<\/div>/);
assert.match(code, /<div/g);
assert.equal(code.match(/<\/div>/g).length, code.match(/<div/g).length);
这是对所有 <div> 的「数量守恒」检查:源码里 <div 与 </div> 出现次数必须相等。新增一行栅格 = 新增 3 个开标签(外层 row + 内层两个 col-xs-6),就必须补上同样数量的 3 个闭合标签,否则缩进/嵌套再漂亮也会被判失败。这也是新手最常见错误(漏闭合)被机器自动揪出的典型场景。
六、种子代码与参考解法:对照式学改造
关卡从一段可运行的 CatPhotoApp 页面开始(--seed--,第 45-107 行),页面已经包含响应式标题、三枚 Font Awesome 按钮、图片、爱好列表和一个 form。种子代码中 radio 尚未套栅格,它们和复选框、文本输入框一样平铺在 form 里:
<link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet" type="text/css">
<style>
h2 {
font-family: Lobster, Monospace;
}
.thick-green-border {
border-color: green;
border-width: 10px;
border-style: solid;
border-radius: 50%;
}
</style>
<div class="container-fluid">
<div class="row">
<div class="col-xs-8">
<h2 class="text-primary text-center">CatPhotoApp</h2>
</div>
<div class="col-xs-4">
<a href="#"><img class="img-responsive 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>
</div>
</div>
<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"><i class="fa fa-thumbs-up"></i> Like</button>
</div>
<div class="col-xs-4">
<button class="btn btn-block btn-info"><i class="fa fa-info-circle"></i> Info</button>
</div>
<div class="col-xs-4">
<button class="btn btn-block btn-danger"><i class="fa fa-trash"></i> Delete</button>
</div>
</div>
<p>Things cats <span class="text-danger">love:</span></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>
对照参考解法(--solutions--,第 109-175 行),本关要求的改动非常小、非常有针对性——只动 form 内部 radio 的部分:在 <form> 里、两个 radio 的 <label> 外层包一层 div.row,再各自包一层 div.col-xs-6。复选框、输入框、提交按钮保持原样(它们属于后续关卡的任务范围):
<form action="https://freecatphotoapp.com/submit-cat-photo">
<div class="row">
<div class="col-xs-6">
<label><input type="radio" name="indoor-outdoor"> Indoor</label>
</div>
<div class="col-xs-6">
<label><input type="radio" name="indoor-outdoor"> Outdoor</label>
</div>
</div>
<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>
改写前后的结构差异可以用下面的层次图概括(仅展示 radio 区域):
改造前: 改造后:
form form
├─ label ▸ input[radio] Indoor ├─ div.row
└─ label ▸ input[radio] Outdoor │ ├─ div.col-xs-6 ▸ label ▸ input[radio] Indoor
│ └─ div.col-xs-6 ▸ label ▸ input[radio] Outdoor
└─ ...
- 每个
col-xs-6占 50% 宽,两块并列 = 一整行;因为宽度是百分比计算,无论设备是 320px 的旧手机还是 4K 显示器,两个选项都会各自占据一半宽度、从「上下堆叠的标签」变成视觉上「左右均匀分布的两列」。 label包裹input[radio]的既有结构保持不变——点击文字(Indoor / Outdoor)即可选中对应选项的可用性不被破坏,这正是栅格只负责"外部分布"、不干预"内部控件"的体现。- 同组的两个 radio 仍共享
name="indoor-outdoor",属于同一单选组,一次只能选一个的语义由 name 属性保证,与栅格无关。
七、动手自测与常见错误排查
按原文档的三条 hints,可以在本地手动复现测试逻辑。把解法 HTML 保存到文件后在浏览器 DevTools Console 中执行下面任一查询来验证:
// 应返回 NodeList,长度为 2
document.querySelectorAll('div.row input[type=radio]');
// 应返回 NodeList,长度为 2
document.querySelectorAll('div.col-xs-6 input[type=radio]');
常见错误与对应提示快速对照:
| 错误写法 | 后果 | 命中的断言 |
|---|---|---|
| 两个 radio 直接平铺、不加 row/col | 第 1、2 条断言均为 0 命中 | 断言 1、2 失败 |
只在外面加 div.row、漏掉各自的 div.col-xs-6 |
radio 虽是 row 后代,但没有 col 命中 | 断言 2 失败 |
两个 radio 放进同一个 col-xs-6 |
col-xs-6 内 radio 数量命中只有 1 |
断言 2 失败 |
新增 div 忘记写 </div> |
开闭标签数量不相等 | 断言 3 失败 |
把 col-xs-6 直接加到 <input> 上 |
选择器 div.col-xs-6 input 无法命中 |
断言 2 失败 |
八、从本关出发的后续学习路径
本关是「把 Bootstrap 栅格应用到表单」系列的开端。按照 curriculum/structure/blocks/bootstrap.json 中 challengeOrder 的顺序,紧接着的关卡与本文形成递进关系:
- Responsively Style Checkboxes(bad87fee1348bd9aeda08845.md):三个复选框分别放入
col-xs-4,进一步印证 12 列拆分法则; - Style Text Inputs as Form Controls:文本输入框套用表单控件样式;
- Line up Form Elements Responsively with Bootstrap:把单选、复选、文本输入等所有控件整体放进栅格排版。
可以看到 freeCodeCamp 把「一个可迁移的 CSS 概念」拆解成一组小步练习:先用按钮与图片学会 row/col-xs-*,再在本关把它迁移到 radio,下一关迁移到 checkbox,最后做整表单布局——每一步只引入一个新变量。阅读关卡源码时,同时对照 curriculum/structure/blocks/bootstrap.json 的 challengeOrder 与各自文件中的 --seed--/--solutions--,会发现每个解法都只在上一个解法的结果上做最小增量修改,这本身就是一份很好的"响应式表单布局最小改造"的示范序列。
九、小结
Responsively Style Radio Buttons 这一关用一次极小的 HTML 结构调整,讲透了三个可长期复用的要点:
- Bootstrap 的
col-xs-*栅格类可作用在包括表单控件在内的任意元素上,本关用它让两个单选按钮在任意分辨率下各占半行; - 栅格负责外部布局、label 负责控件语义,二者各司其职,结构上通过
div.row > div.col-xs-6 > label > input[radio]逐层嵌套表达; - 在 freeCodeCamp 中,一切以
--hints--里的 DOM 断言为准——理解document.querySelectorAll选择器如何验证层级,就掌握了这类关卡通过与否的根本判据。
你可以直接在仓库中打开 curriculum/challenges/english/blocks/bootstrap/bad87fee1348bd9aedb08845.md 阅读原文,结合 curriculum/structure/blocks/bootstrap.json 了解它在整个 Bootstrap 模块中的编排位置,然后动手把 radio、checkbox、文本输入组合成一张真正响应式的表单页。
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 StartedRust0629
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