首页
/ freeCodeCamp 栅格实战:用 Bootstrap 的 col-xs-6 让 Radio Buttons 自适应布局

freeCodeCamp 栅格实战:用 Bootstrap 的 col-xs-6 让 Radio Buttons 自适应布局

2026-09-07 14:59:15作者:昌雅子Ethen

本指南围绕 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 on form elements, too! This way, our radio buttons will be evenly spread out across the page, regardless of how wide the screen resolution is.

也就是说,Bootstrap 栅格类不仅能用在图片、按钮上,同样适用于 form 表单里的元素。如果把一对单选按钮各自放进一列栅格,它们就能在任意屏幕宽度下都平分一行、保持均匀间距,而不是挤在表单左侧。

关卡任务拆解

  1. 把两个 radio 单选按钮一起嵌套进一个 <div class="row">
  2. 再把每个单选按钮分别嵌套进一个 <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 Checkboxesbad87fee1348bd9aeda08845.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.jsonbootstrap 列在 Front End Development Libraries(前端开发库)超级块之下,与 jquery、sass、react、redux 等并列。

也就是说,这段讲解栅格的 markdown 不是孤立的教程片段,而是被 challengeOrder 精确编排、会被课程构建流水线消费的真实课程内容。挑战文件的 YAML frontmatter(第 1-11 行)提供了完整元数据:

字段 本关取值 含义
id bad87fee1348bd9aedb08845 全局唯一挑战 ID,被 bootstrap.jsonchallengeOrder 引用
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.jsonchallengeOrder 的顺序,紧接着的关卡与本文形成递进关系:

  1. Responsively Style Checkboxesbad87fee1348bd9aeda08845.md):三个复选框分别放入 col-xs-4,进一步印证 12 列拆分法则;
  2. Style Text Inputs as Form Controls:文本输入框套用表单控件样式;
  3. Line up Form Elements Responsively with Bootstrap:把单选、复选、文本输入等所有控件整体放进栅格排版。

可以看到 freeCodeCamp 把「一个可迁移的 CSS 概念」拆解成一组小步练习:先用按钮与图片学会 row/col-xs-*,再在本关把它迁移到 radio,下一关迁移到 checkbox,最后做整表单布局——每一步只引入一个新变量。阅读关卡源码时,同时对照 curriculum/structure/blocks/bootstrap.jsonchallengeOrder 与各自文件中的 --seed--/--solutions--,会发现每个解法都只在上一个解法的结果上做最小增量修改,这本身就是一份很好的"响应式表单布局最小改造"的示范序列。

九、小结

Responsively Style Radio Buttons 这一关用一次极小的 HTML 结构调整,讲透了三个可长期复用的要点:

  1. Bootstrap 的 col-xs-* 栅格类可作用在包括表单控件在内的任意元素上,本关用它让两个单选按钮在任意分辨率下各占半行;
  2. 栅格负责外部布局、label 负责控件语义,二者各司其职,结构上通过 div.row > div.col-xs-6 > label > input[radio] 逐层嵌套表达;
  3. 在 freeCodeCamp 中,一切以 --hints-- 里的 DOM 断言为准——理解 document.querySelectorAll 选择器如何验证层级,就掌握了这类关卡通过与否的根本判据。

你可以直接在仓库中打开 curriculum/challenges/english/blocks/bootstrap/bad87fee1348bd9aedb08845.md 阅读原文,结合 curriculum/structure/blocks/bootstrap.json 了解它在整个 Bootstrap 模块中的编排位置,然后动手把 radio、checkbox、文本输入组合成一张真正响应式的表单页。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388