freeCodeCamp Basic HTML 挑战精讲:用 input 元素创建文本输入框(Create a Text Field)
本篇文章以 freeCodeCamp 开源课程仓库中 "Basic HTML and HTML5" 板块的第 17 个挑战 Create a Text Field(挑战文件 bad87fee1348bd9aedf08829.md)为核心,系统讲解 HTML <input type="text"> 文本输入框的语法与"空元素(void element)"这一底层概念,并逐段拆解挑战的 description、instructions、hints、seed 与 solutions 结构。读完本文,你将掌握文本输入框的标准写法、input 元素不可有闭合标签的原因,理解 freeCodeCamp 挑战文件是如何被"机器可读"地设计、校验与自动判题的,并能跟随 CatPhotoApp 项目一路构建出可提交的表单。
一、挑战档案与它在课程中的位置
先看挑战文件的 YAML frontmatter 头:
---
id: bad87fee1348bd9aedf08829
title: Create a Text Field
challengeType: 0
videoUrl: 'https://scrimba.com/p/pVMPUv/c2EVnf6'
forumTopicId: 16823
dashedName: create-a-text-field
---
其中几个字段值得关注:
| 字段 | 值 | 含义 |
|---|---|---|
id |
bad87fee1348bd9aedf08829 |
全局唯一的挑战 ID,既是文件名,也是数据库与测试中定位该挑战的稳定标识 |
challengeType |
0 |
挑战类型。在 packages/shared/src/config/challenge-types.ts 第 1 行定义了 const html = 0;,即类型 0 表示经典 HTML 练习 |
videoUrl |
Scrimba 教学视频 | 该挑战配套的短视频讲解 |
forumTopicId |
16823 |
关联论坛讨论主题的编号 |
dashedName |
create-a-text-field |
URL 友好的短名称 |
这个挑战不是孤立存在的。查询板块定义文件 basic-html-and-html5.json 中的 challengeOrder 数组可以发现,整个板块共 28 个挑战,由前向后依次推进:从 h2 标题、p 段落、图片 <img>、超链接 <a>、无序列表 <ul>、有序列表 <ol>,再到本关——第一次引入"表单交互"概念,随后依次是 "Add Placeholder Text to a Text Field"、"Create a Form Element"、"Add a Submit Button to a Form"、"Use HTML5 to Require a Field"、单选按钮与复选框等。
也就是说,本关是整条"构建网页表单"学习路径的起点:在此之后文本输入框会被装进 <form>、加上 placeholder、配上 submit 按钮,最终形成完整的用户输入机制。
二、核心知识点:input 元素与文本输入框
挑战描述原文非常简洁,核心只有两句话:
input元素是从用户那里获取输入的一种便捷方式;- 创建一个文本输入框只需一行代码:
<input type="text">
展开来看,这行代码包含两个组成部分:
- 标签名
input:input是 HTML 中专门用于收集用户输入的内联表单控件。根据type属性的不同取值,它可以呈现为文本框、密码框、数字框、邮箱框、日期选择器、单选按钮等多种形态。 type属性:type="text"显式声明这是一个单行纯文本输入框。在 HTML5 中,type默认值本来就是text,但显式写出是更严谨、可读性更好的实践——这也是后续挑战 hints 会专门用input[type=text]选择器来校验的原因。
这个元素在浏览器中渲染为一个可键入字符的单行编辑框。值得注意的是:本挑战只要求在页面中加入一个输入框,并不要求它具备提交能力——把文本框"交给"服务器、实现真正提交,是后面几个挑战(<form> 元素与 submit 按钮)的任务。这种循序渐进、一次只引入一个新概念的设计,正是 freeCodeCamp 挑战体系的典型教学策略。
三、深入理解:为什么 input 是"空元素(void element)"
挑战描述特别强调了一句话:
Note that
inputis a void element.
这是本关最容易出错、也最值得深挖的知识点。"void element(空元素 / 无内容元素)"是 HTML 规范术语,指没有内容、也没有闭合标签的元素。它只能以自闭合形式或仅写开始标签形式出现:
<!-- 合法:HTML5 中 void 元素不应写闭合标签 -->
<input type="text">
<!-- 也是合法写法(XML/XHTML 风格的自闭合,浏览器同样接受) -->
<input type="text"/>
而下面这种"既写开始标签又写闭合标签"的写法是不合法的:
<!-- 非法:input 没有内容,不允许闭合标签 -->
<input type="text"></input>
HTML 标准中常见的 void 元素除 input 外还有 img(图片)、br(换行)、hr(水平分隔线)、meta、link 等。它们的共同特征是:自身即承载全部信息(通过属性),不需要也不可能包裹任何子内容。对比一下同板块中已学过的元素就能理解这种差异:
| 元素 | 是否有闭合标签 | 原因 |
|---|---|---|
p、h2、a、ul、ol、li |
有 | 需要包裹文本或子元素作为"内容" |
img、input、br、hr |
无 | void 元素,没有内容模型,数据全靠属性表达 |
这一知识点在后续挑战中会被反复验证:例如下一关 "Add Placeholder Text to a Text Field"(bad87fee1348bd9aedf08830.md)的 hints 中就有专门的断言 assert.notMatch(code, /<input.*\/?>.*<\/input>/gi),用于确保学习者没有给 input 错误地加上闭合标签。可见"void 元素"不是细枝末节的考点,而是整个表单学习阶段持续强调的规范。
四、逐句读懂题目指令与初始代码(seed)
4.1 指令解读
挑战的 --instructions-- 只有一句话:
Create an
inputelement of typetextbelow your lists.
即:在页面中已有的两个列表之后,新增一个 <input type="text">。注意"below your lists"的约束——正确的插入位置是在 <ol> 列表的 </ol> 之后、</main> 之前。
4.2 seed 初始代码分析
挑战提供了一段完整的 CatPhotoApp 初始代码作为练习起点(即 --seed-contents--):
<h2>CatPhotoApp</h2>
<main>
<p>Click here to view more <a href="#">cat photos</a>.</p>
<a href="#"><img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a>
<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>
</main>
这段代码相当于前面十几个挑战的"集大成",涵盖了:
h2页面主标题;- 包裹在
p中、指向#死链接的<a>锚点; - 由
<a>包起来的<img>图片(alt属性描述了图片内容); ul无序列表 + 3 个li;ol有序列表 + 3 个li;- 语义化容器
<main>。
而学习者的任务就是在倒数第二个空行处补上文本框。可以看到 seed 中 <ol> 与 </main> 之间留有空白行,这正是设计给学习者"插入新元素"的位置提示。
五、hints 背后的自动判题机制
挑战的 --hints-- 段不仅写给学习者,更是机器判题的依据。本关唯一的 hint 是:
assert.isNotEmpty(document.querySelectorAll('input[type=text]'));
这条断言逐层拆解:
document.querySelectorAll('input[type=text]'):在用户渲染出的完整 DOM 中,查找所有type属性为text的input元素,返回一个类数组的 NodeList;assert.isNotEmpty(...):断言该 NodeList 不为空,即页面中确实存在至少一个input[type=text]。
可以看出,freeCodeCamp 的自动判题是在浏览器环境中直接对学习者代码渲染后的真实 DOM 做断言,而不是做简单的字符串匹配。从 assert.isNotEmpty 的调用风格可以推断,其运行时采用 Chai 风格的断言库,这与同板块其他挑战(如后续挑战中出现的 assert.strictEqual、assert.match)保持一致。这种"运行真实代码 + DOM 级验证"的评测方式,保证了答案即便在标签书写顺序、空白字符上有所差异,只要语义正确就能通过。
挑战类型与评测方式之间有明确的映射关系。在 packages/shared/src/config/challenge-types.ts 中,challengeType: 0 被定义并命名为 html;同时 curriculum/schema/challenge-schema.js 对挑战文件做出强校验(约第 180 行),要求 challengeType 必须是 0 到 33 之间的数字,并针对不同类型的挑战要求不同的必需字段(例如视频类挑战需要 videoId,填空题类挑战需要 inputType 等)。这意味着每个 .md 挑战文件在被正式纳入课程前,都要通过 JSON Schema 级的格式验证,保证 --description--、--instructions--、--hints--、--seed--、--solutions-- 等区块结构完整且类型合法。
六、参考解答:注意官方把文本框放进了 form
--solutions-- 段给出了官方参考实现。与 seed 相比,唯一的差异是在列表之后新增了一个 <form> 包裹的文本输入框:
<h2>CatPhotoApp</h2>
<main>
<p>Click here to view more <a href="#">cat photos</a>.</p>
<a href="#"><img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a>
<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>
<input type="text">
</form>
</main>
官方解答比题目要求多走了一步——它把 <input type="text"> 包进了一个空 <form> 元素。这样做的用意有两层:一是从语义上讲,input 作为表单控件理应存在于 <form> 之中;二是为紧随其后的挑战做铺垫——下一关将在同一个 <form> 内给 input 添加 placeholder(bad87fee1348bd9aedf08830.md),再下一关会让 <form> 带上 action 属性(bad87fee1348bd9aede08830.md),随后加入 type="submit" 的按钮(bad87fee1348bd9aedd08830.md),最终让整个表单真正把用户输入的数据提交到指定 URL。
七、由本关引申的 input 常用属性速览
尽管本关只要求最简写法,理解 input 的常用属性能帮助你判断后续挑战的演进逻辑(后续每一关基本都是在"补一个属性"):
| 属性 | 作用 | 对应后续挑战 |
|---|---|---|
type |
定义控件类型,text 表示单行文本 |
本关 |
placeholder |
输入前显示在框内的灰色提示文字 | Add Placeholder Text to a Text Field |
action(在 form 上) |
提交数据的去向 URL | Create a Form Element |
type="submit"(在 button 上) |
触发表单提交 | Add a Submit Button to a Form |
required |
声明该字段必填 | Use HTML5 to Require a Field |
name |
提交时标识该字段的键名 | 表单提交数据格式的基础 |
value |
控件当前值(或默认值) | 单选/复选框相关挑战 |
从知识树角度看,本关引入的 <input type="text"> 是理解后续 radio、checkbox、submit 等所有输入型控件的基础——它们的差异本质上只是 type 属性取值不同而已。
八、在仓库中如何定位、校验与进一步研究本挑战
本关对应的源文件是 curriculum/challenges/english/blocks/basic-html-and-html5/bad87fee1348bd9aedf08829.md,英文原文是其他语言翻译的基准(仓库中的 i18n-curriculum 目录用于存放各语言的翻译成果)。若想在本地完整验证这类挑战文件,可以关注以下仓库内证据链:
- 板块顺序与元信息:curriculum/structure/blocks/basic-html-and-html5.json 中的
challengeOrder精确记录了每个挑战的 ID、标题与先后次序; - 挑战类型体系:packages/shared/src/config/challenge-types.ts 定义了从
html(0)到freeCodeCampOsCert(33)共 34 种挑战类型; - 挑战文件结构校验:curriculum/schema/challenge-schema.js 用 Joi 对每个
.md挑战做 Schema 级校验,保证id、title、challengeType及各内容区块合法; - 课程构建入口:在 curriculum/src/get-challenges.ts 中可以看到
getChallengesForLang(lang, filters)按语言读取并构建课程数据的流程,英文源文件是判断某翻译是否存在的基准(hasEnglishSource函数)。
本挑战的全部要点可以浓缩为一句话:用一个 void 元素 input,配合 type="text" 属性,在列表下方放一个文本输入框——看似简单的一步,却同时引入了"表单控件"和"空元素"两个贯穿后续所有表单类挑战的基础概念,是整个 freeCodeCamp CatPhotoApp 表单学习线真正意义上的起点。
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 StartedRust0624
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