freeCodeCamp 课程挑战文件实战解析:用 hr 元素在卡片中创建水平分割线
本文以 freeCodeCamp 开源课程库中的经典 HTML/CSS 挑战《Create a Horizontal Line Using the hr Element》为核心,完整解读挑战文档的字段与结构约定、种子代码(seed)与标准答案的构造方法,以及如何通过断言测试验证 <hr> 水平分割线的正确插入位置。读完后你将掌握该课程挑战文件的 Markdown 分节规范、void 元素 <hr> 的实际应用场景,以及前后挑战如何围绕同一张卡片递进演进的编写思路。
挑战在课程中的位置
该挑战文件位于英文课程目录下的 Applied Visual Design(应用视觉设计)模块:
- 挑战文件:curriculum/challenges/english/blocks/applied-visual-design/587d781b367417b2b2512abb.md
- 模块结构定义:curriculum/structure/blocks/applied-visual-design.json
从 applied-visual-design.json 的 challengeOrder 数组可以看到,该挑战(id 为 587d781b367417b2b2512abb)被安排在“Use the s Tag to Strikethrough Text”(删除线)之后、“Adjust the background-color Property of Text”(调整背景色)之前,模块的 helpCategory 标注为 HTML-CSS。整个模块由若干短小的挑战按序编排,共同围绕同一张“个人资料卡片”逐步搭建:前面的挑战为卡片的 h4 标题加入删除线文字,本挑战为标题与正文之间加一条水平线,后面的挑战再修改 h4 的背景色与内边距。这种“一张卡片逐步成型”的编排方式,是本模块最核心的教学设计。
挑战文档的结构规范
freeCodeCamp 的每个挑战都是一个带 YAML front matter 的 Markdown 文件,通过 # 一级标题分隔出若干固定分节。本挑战文件的完整骨架如下:
---
id: 587d781b367417b2b2512abb
title: Create a Horizontal Line Using the hr Element
challengeType: 0
videoUrl: '...'
forumTopicId: 301049
dashedName: create-a-horizontal-line-using-the-hr-element
---
# --description-- 知识点讲解
# --instructions-- 给学员的具体任务指令
# --hints-- 分步提示,每条提示附对应测试断言
# --seed--
## --seed-contents-- 学员打开编辑器时的初始代码
# --solutions-- 参考解答
front matter 各字段的作用:
| 字段 | 本挑战取值 | 说明 |
|---|---|---|
id |
587d781b367417b2b2512abb |
挑战唯一标识(MongoDB ObjectId),结构文件中的 challengeOrder 即按此 id 排序 |
title |
Create a Horizontal Line Using the hr Element | 挑战标题 |
challengeType |
0 |
挑战类型,用于区分经典编辑器挑战与测验等其他类型(类型定义见 packages/shared/src/config/challenge-types.ts) |
videoUrl |
外部视频教程地址 | 可选的教学视频 |
forumTopicId |
301049 |
关联的官方论坛主题编号 |
dashedName |
create-a-horizontal-line-using-the-hr-element |
挑战的稳定 slug,用于生成 /learn/ 路由路径 |
挑战字段与分节的合法性由课程 schema 校验,相关校验逻辑位于 curriculum/schema/challenge-schema.js,其中使用 Joi 定义了 fileJoi(含 seed、contents 等代码文件字段)、prerequisitesJoi 等对象结构,课程构建时会依据 curriculum/package.json 中的测试流程对挑战文件做校验,保证每个挑战的结构完整、可被编辑器正确加载。
核心知识点:hr 是用于视觉分组的水平分割线
本挑战的 --description-- 分节给出了 <hr> 元素的标准定义:
你可以使用
hr标签在其包含元素的宽度范围内绘制一条水平线。它可以用来表示话题的切换,或者在视觉上分隔不同组的内容。
结合 --instructions-- 分节的补充说明,需要掌握两个要点:
- 用途:
<hr>表达的是“主题/内容分组的边界”这一语义,而不是单纯的装饰。在本挑战中,它恰好把卡片的标题区(h4)与介绍区(p)划分成两个视觉区块。 - void 元素:在 HTML 中
<hr>是 void element(空元素),不需要单独的闭合标签,写<hr>即可,写成<hr/>也合法。这也是后文测试断言要同时兼容两种写法的原因。
种子代码(seed)解析
--seed-contents-- 分节中给出了学员打开编辑器时的完整初始代码。它是一张已具备样式的卡片:
<style>
h4 {
text-align: center;
height: 25px;
}
p {
text-align: justify;
}
.links {
text-align: left;
color: black;
}
.fullCard {
width: 245px;
border: 1px solid #ccc;
border-radius: 5px;
margin: 10px 5px;
padding: 4px;
}
.cardContent {
padding: 10px;
}
.cardText {
margin-bottom: 30px;
}
</style>
<div class="fullCard">
<div class="cardContent">
<div class="cardText">
<h4><s>Google</s>Alphabet</h4>
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
</div>
<div class="cardLinks">
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
</div>
</div>
</div>
几个值得注意的衔接细节:
<h4>中的<s>Google</s>Alphabet正是上一挑战(587d781b367417b2b2512aba.md,“Use the s Tag to Strikethrough Text”)的作答结果被固化成了本挑战的 seed,直观体现了模块的递进关系;- 任务指令要求把
<hr>加在“包含卡片标题的h4下方”,从 seed 结构看,也就是在</h4>与<p>之间(seed 中该行恰好留有一个空行作为提示位); p内的em、u、strong标签同样来自前序挑战(斜体、下划线、加粗)的作答,cardLinks区则属于后续挑战(hover 状态、链接字号等)的改造对象。
标准答案与最小改动
--solutions-- 分节给出的参考解答与 seed 相比只有一行差异——在 h4 与 p 之间插入 <hr>:
<div class="fullCard">
<div class="cardContent">
<div class="cardText">
<h4><s>Google</s>Alphabet</h4>
<hr>
<p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
</div>
<div class="cardLinks">
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
</div>
</div>
</div>
样式部分(<style> 块)与 seed 完全一致,说明本挑战考察的只是语义元素的插入位置,不要求任何 CSS 改动。<hr> 作为块级元素,会自动横跨其包含元素 .cardText 的内容宽度,配合 .cardText 的 margin-bottom: 30px,标题与正文之间形成清晰的视觉隔断。
测试断言解析:测试如何验证 hr 的正确性
--hints-- 分节中的每条提示都附带一条可执行的断言,它们也是平台判定挑战通过的最终依据:
断言一:页面中恰好存在一个 hr
assert.lengthOf(document.querySelectorAll('hr'),1);
通过 DOM API 统计 <hr> 数量,确认学员确实在标记中添加了水平线,且没有多余元素。
断言二:hr 必须位于标题与段落之间
assert.match(code,/<\/h4>\s*?<hr(>|\s*?\/>)\s*?<p>/gi);
这条正则直接匹配源代码文本,拆解如下:
| 片段 | 含义 |
|---|---|
<\/h4> |
以标题结束标签为定位起点 |
\s*? |
允许中间存在任意空白(懒匹配) |
| `<hr(> | \s*?/>)` |
\s*?<p> |
紧随其后必须出现段落开始标签 |
gi 标志使其不区分大小写并可全局匹配。两条断言合起来即完整表达了任务要求:“在标记中添加一个 hr,且它必须夹在标题和段落之间”——与 --instructions-- 的文字描述一一对应。
小结:从一张挑战文件看课程编写范式
这篇挑战文件虽然内容简短,却完整覆盖了 freeCodeCamp 挑战编写的全部要素:front matter 元数据(id、dashedName、forumTopicId)、--description-- 中的知识点陈述、--instructions-- 的可执行任务、带断言的 --hints-- 分步提示、可直接运行的 --seed-contents--,以及最小 diff 的 --solutions--。再结合 applied-visual-design.json 的排序与前后挑战(删除线挑战、背景色挑战)对同一张卡片的持续改造,可以看到该课程模块通过“小步递进、断言兜底”的方式,让 <hr> 这类基础 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 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