深入解析 freeCodeCamp Bootstrap 挑战:用 col-xs-6 拆分栅格行(Split Your Bootstrap Row)
本文以 freeCodeCamp 开源课程中 Bootstrap 模块的第 22 个挑战 Split Your Bootstrap Row(挑战原文件)为核心,系统讲解 Bootstrap 3 栅格系统中 row 与列(column)的搭配方式、col-xs-6 的宽度计算原理,同时结合仓库内 challenge 文件的 YAML 结构、校验 Schema 与自动化测试断言,帮助读者既能在课程中顺利通关,也能真正理解 Bootstrap 栅格背后的布局机制,并看懂 freeCodeCamp 挑战题是如何编写与校验的。
一、挑战背景:正在拼装的 jQuery Playground
在进入本挑战之前,我们需要先弄清它在整个课程块中的位置。打开 bootstrap 块的结构清单 可以看到,bootstrap 块的 helpCategory 为 HTML-CSS,blockLayout 为 legacy-challenge-list,其中 required 字段声明了本块所有挑战运行时的外部依赖——通过 CDN 引入的 twitter-bootstrap/3.3.7/css/bootstrap.css。也就是说,整个 Bootstrap 模块基于 Bootstrap 3.3.7 完成。
从 challengeOrder 数组可以看出一个清晰的"搭积木"式教学序列:
Use Responsive Design with Bootstrap Fluid Containers、House our page within a Bootstrap container-fluid div—— 用container-fluid包住页面,保证移动端自适应;Create a Bootstrap Headline—— 创建带text-primary text-center的h3标题;Create a Bootstrap Row—— 在标题下方创建div.row;Split Your Bootstrap Row(本文主题) —— 将这一行拆成两列;Create Bootstrap Wells、Add Elements within Your Bootstrap Wells…… —— 在每一列中放入well容器并逐步填充内容。
这些步骤最终拼装成一个日后用于 jQuery 教学的交互小页面(jQuery Playground)。本挑战的角色正是承上启下的"分列"一步:上一挑战(Create a Bootstrap Row)把内容装进 row,而本挑战要把 row 内部划分为两个并排的 col-xs-6 列,为下一步 Create Bootstrap Wells 在每列中嵌套内容做准备。
二、任务目标拆解:row 内部建两个 col-xs-6
挑战描述原文要求:
Now that we have a Bootstrap Row, let's split it into two columns to house our elements. Create two
divelements within your row, both with the classcol-xs-6.
即:在现有 row div 内部创建 两个 div 元素,且两者都带有 col-xs-6 类,让它们并排承载后续内容。
挑战给定的起始代码(--seed-contents-- 段)如下:
<div class="container-fluid">
<h3 class="text-primary text-center">jQuery Playground</h3>
<div class="row">
</div>
</div>
可以看到整体 DOM 骨架为 container-fluid(全宽流体容器)→ h3 标题 → row(栅格行),而 row 内部目前是空的。我们所需的全部改动,就是往空的 row 中补上两个带 col-xs-6 的 div。
三、栅格原理:为什么 col-xs-6 恰好是"一行两列"
要真正理解本挑战而不只是"照抄答案",需要明白 Bootstrap 3 的 12 栏栅格(grid)系统:
- Bootstrap 3 把每一行(
.row)横向划分为最多 12 列; - 列宽通过
col-断点前缀-跨度形式的类控制,前缀xs/sm/md/lg代表不同的屏幕尺寸档位; - 在本项目实际使用的 Bootstrap 3.3.7(见 bootstrap.json 中
required声明的 CSS 地址)中,xs对应"极小屏幕及以上",其列样式不依赖任何媒体查询,即基础默认生效的规则; col-xs-6的含义是"占用 12 栏中的 6 栏",即 6 / 12 = 50% 宽度。两个col-xs-6并列时恰好铺满整行,形成左右各半的两栏布局。
因此本挑战的解法本质是:把 row 这个"12 栏"横向切成两份,每份 6 栏,从而并排安置两个将来要放内容的列容器。
四、标准解法与逐步推演
--solutions-- 段给出的官方标准答案如下:
<div class="container-fluid">
<h3 class="text-primary text-center">jQuery Playground</h3>
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-6"></div>
</div>
</div>
对照原始 seed,逐层核对:
| 层级 | 元素 | 作用 |
|---|---|---|
| 1 | div.container-fluid |
全宽流体容器,让内容适配任意屏幕宽度 |
| 2 | h3.text-primary.text-center |
标题(上一挑战产物,保持不动) |
| 3 | div.row |
栅格行,规定横向 12 栏的布局上下文 |
| 4 | div.col-xs-6 × 2 |
两列,各占 6/12 = 50% 宽度,左右并排 |
在动手时可借助"缩进即嵌套关系"的技巧自查:两个 col-xs-6 必须作为 row 的直接子元素出现,并且全部标签成对闭合(<div> 与 </div> 一一对应)。完成本挑战并跑通后,下一挑战会要求在每个 col-xs-6 内部再嵌套一个 div.well,形成"列中套容器"的更深一层结构。
五、验证标准(hints)逐条解读
freeCodeCamp 的每个挑战都可以看作一份带自动化校验的"教学测试用例"。本挑战的 --hints-- 段用 JavaScript 断言规定了验收标准,理解它们有助于反过来把握 Bootstrap 栅格结构的约束。
提示 1:结构约束——列必须嵌套在行内
assert.lengthOf(document.querySelectorAll('div.row > div.col-xs-6'),2);
该断言使用 CSS 子选择器 div.row > div.col-xs-6 精确匹配 row 的直接子级 col-xs-6,并要求数量为 2。这意味着:
- 两个列元素必须用
class="col-xs-6"(不能是别的宽度类); - 它们必须是
row的直接子元素,多包一层中间 div 会导致选择器失配; - 恰好两个,缺一个或多一个都无法通过。
提示 2:标签闭合完整性
assert.match(code,/<\/div>/g);
assert.match(code,/<div/g);
assert.equal(code.match(/<\/div>/g).length , code.match(/<div/g).length);
这里把用户的源码当作文本,分别用正则在整段代码中统计 <div 与 </div> 的出现次数,并要求二者相等。结合前面几关(如 container-fluid 挑战、Create a Bootstrap Row)中完全一致的断言写法可以看出,这一规则是整套 HTML 类挑战的通用卫生检查,专门用来防止用户写出未闭合的 div 标签。
六、挑战文件的内部结构:一份可学习的 YAML 模板
本挑战所在的 md 文件本身就是 freeCodeCamp 课程内容的"源数据格式"。理解它的结构,等于同时学会了阅读或编写课程块里任意一道同类挑战:
---
id: bad87fee1348bd9aec908847
title: Split Your Bootstrap Row
challengeType: 0
forumTopicId: 18306
dashedName: split-your-bootstrap-row
---
各字段含义如下:
id:挑战全局唯一标识,同时被 bootstrap.json 的challengeOrder引用,用于把挑战组织进块内顺序;title:在课程导航、题目界面中展示的标题;challengeType:挑战类型编号。在 challenge-types.ts 中const html = 0,即本挑战属于纯 HTML 类(type 0);forumTopicId:关联的社区论坛话题 ID,属于历史遗留字段;dashedName:URL 化的 slug 形式标题(小写字母 + 连字符)。
Front matter 之下依次是四个用 Markdown 标题切分的正文段落:
# --description--:题目说明(含任务描述,可能内嵌示例);# --hints--:验收断言列表,每条 hint 是一段说明文字 + 一段可执行的 JS 代码;# --seed--/## --seed-contents--:编辑器初始代码;# --solutions--:官方参考解法。
这些字段并非随意约定。仓库中的 challenge-schema.js 使用 Joi 对每道挑战做结构化校验,例如 challengeType 被限定为 0~33 的数字、dashedName 必须匹配 slug 正则、forumTopicId 为数字等。这也是 freeCodeCamp 能保证上万道挑战格式统一、可被构建管线稳定消费的底层机制之一。
七、运行与验证方式
Bootstrap 块已通过 bootstrap.json 的 required 声明自动注入 Bootstrap 3.3.7 样式,因此只需把 HTML 内容粘贴到课程自带的编辑器即可实时预览;两个 col-xs-6 会让后续要放入的两个内容区块并排各占半行。若想在本地独立验证栅格效果,可新建一个 HTML 页面,在 <head> 中引入与课程一致的样式表并放入上述解法代码,将浏览器窗口从宽缩窄即可观察到两列始终保持 50% 等宽布局(因为 xs 档位没有最小宽度门槛)。
八、小结
Split Your Bootstrap Row 表面上只是一个"加两个 div"的小练习,但其背后串联了三层知识:
- 栅格使用姿势:
.row必须位于容器内,列必须是.row的直接子元素,列跨度之和为 12 时恰好铺满一行; - 断点前缀语义:
col-xs-6在 Bootstrap 3 中代表"默认始终生效、占 6/12 宽度",是理解sm/md/lg等响应式档位的前提; - 挑战工程化校验:通过 challenges 原始 md 文件 中 hints 的 DOM 断言与标签闭合断言,可以看到 freeCodeCamp 如何把一道 HTML 题目转译成可自动判分的测试用例。
掌握"容器—行—列"这三层结构与 12 栏分配逻辑后,继续阅读下一个挑战 Create Bootstrap Wells 时,你会自然理解为什么 well 必须被嵌进 col-xs-6 内部——因为列才是 Bootstrap 栅格中真正承载内容的布局单元。
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 StartedRust0630
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
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