用 id 选择器为单个元素定制样式:freeCodeCamp basic-css 课程实战解析
本篇以 freeCodeCamp 课程仓库中 basic-css 模块的挑战文档 Use an id Attribute to Style an Element 为主体,讲清 CSS id 选择器的语法、id 与 class 在选择优先级上的差异,以及该挑战在 CatPhotoApp 项目中的完整实操流程(含种子代码、验收测试与参考答案)。读完后你将掌握:如何用 # 前缀精确选中并样式化页面上唯一的元素、理解 id 高优先级的机制,以及如何从仓库源码层面理解挑战文件是如何被解析、校验和交付给在线编辑器的。
挑战在 basic-css 模块中的位置
basic-css 模块的完整挑战顺序定义在 basic-css.json 中。本挑战的 id 为 bad87dee1348bd9aede07836,标题为 "Use an id Attribute to Style an Element",位于模块第 15 位:
- 前一个挑战是 Set the id of an Element——它为
form元素赋予了id="cat-photo-form",本挑战正是在这个基础上做样式化; - 后一个挑战是 "Adjust the Padding of an Element";
- 模块后段还有 Override Class Declarations by Styling ID Attributes,专门验证"用
id声明覆盖class声明"这一优先级行为。
从这种编排顺序看,课程先把"设置 id"和"用 id 写样式"拆成两步,再在后面用"覆盖"类挑战(如 "Prioritize One Style Over Another")收尾,形成一个关于 CSS 选择器优先级的完整闭环。
核心概念:id 属性与 id 选择器
挑战文档的 --description-- 部分给出了三条核心知识,全部是理解本主题的关键:
-
id可以像class一样被 CSS 选中并样式化。区别在于class可以复用在多个元素上,而id不可复用,应只应用于一个元素。文档原文:However, an
idis not reusable and should only be applied to one element. -
id的 specificity(特异性/重要性)高于class。如果同一个元素上同时存在id样式和class样式且声明了冲突的属性,浏览器会应用id一侧的样式。这为后续 "Override Class Declarations by Styling ID Attributes" 挑战埋下伏笔。 -
选择器前缀规则:在
<style>元素内部,引用class永远在名字前加.,引用id永远在名字前加#。文档给出的示例是:假设页面上有一个id为cat-photo-element的元素,在<style>中这样写即可给它绿色背景:
#cat-photo-element {
background-color: green;
}
id 除了用于 CSS 选择,还有另一个用途:后续挑战会讲到可以用 id 在 JavaScript 中选取并修改特定元素(DOM API 如 document.querySelector('#...')),这也是为什么 id 要求唯一——它是页面元素的"锚点"。
实战任务:给 CatPhotoApp 的表单设置绿色背景
文档 --instructions-- 的任务一句话概括:让当前带有 id="cat-photo-form" 的 form 表单拥有绿色背景。
约束条件(由验收测试反推,见下一节):
form必须保留id="cat-photo-form";form不得添加class或style属性——样式只能通过<style>中的#cat-photo-form规则实现,这是刻意逼你使用 id 选择器而非内联样式或 class;- 绿色需满足计算样式为
rgb(0, 128, 0),即标准green关键字色。
起始代码(seed)
挑战的 --seed-- / --seed-contents-- 小节是学习者打开挑战时编辑器中的初始代码(完整内容见 挑战文档 L64-L127):
<link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet" type="text/css">
<style>
.red-text {
color: red;
}
h2 {
font-family: Lobster, monospace;
}
p {
font-size: 16px;
font-family: monospace;
}
.thick-green-border {
border-color: green;
border-width: 10px;
border-style: solid;
border-radius: 50%;
}
.smaller-image {
width: 100px;
}
.silver-background {
background-color: silver;
}
</style>
<h2 class="red-text">CatPhotoApp</h2>
<main>
<p class="red-text">Click here to view more <a href="#">cat photos</a>.</p>
<a href="#"><img class="smaller-image 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 class="silver-background">
<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>
</div>
<form action="https://freecatphotoapp.com/submit-cat-photo" id="cat-photo-form">
<label><input type="radio" name="indoor-outdoor" checked> Indoor</label>
<label><input type="radio" name="indoor-outdoor"> Outdoor</label><br>
<label><input type="checkbox" name="personality" checked> Loving</label>
<label><input type="checkbox" name="personality"> Lazy</label>
<label><input type="checkbox" name="personality"> Energetic</label><br>
<input type="text" placeholder="cat photo URL" required>
<button type="submit">Submit</button>
</form>
</main>
注意 seed 中 <form> 已经带了 id="cat-photo-form"(由前一个挑战"Set the id of an Element"引入并保留),且 <style> 里还没有任何 #cat-photo-form 规则——这正是学习者需要补齐的部分。
参考答案(solution)
文档 --solutions-- 小节(L131-L197)与 seed 的唯一差异是在 </style> 前追加了一段 id 选择器规则:
#cat-photo-form {
background-color: green;
}
完整的 solution 代码即 seed 代码加上上面这段规则,其余 HTML 结构与属性原封不动。这个最小 diff 恰好演示了文档想传达的要点:给已有 id 的元素加样式,只需在 <style> 中用 #id 写一条规则,无需改动 HTML(前提是 id 已就位)。
验收机制:四条测试如何验证你的实现
挑战的 --hints-- 小节同时承担了"提示"和"判题"双重职责——每条提示附带一段断言代码,学习者的代码必须全部通过才算完成。四条测试如下(与文档原文一致):
测试 1:form 的 id 必须是 cat-photo-form
assert.strictEqual(document.querySelector('form').getAttribute('id'), 'cat-photo-form');
测试 2:form 的背景色必须是绿色
const catPhotoForm = document.querySelector('#cat-photo-form');
const backgroundColor = window.getComputedStyle(catPhotoForm)['background-color'];
assert.strictEqual(backgroundColor, 'rgb(0, 128, 0)');
这里有两个值得注意的技术细节:
- 选择元素本身就用到了
#cat-photo-form这个 id 选择器(document.querySelector接受 CSS 选择器字符串),等于把本挑战的知识点用在了测试代码里; window.getComputedStyle返回的是计算后的样式,颜色会被规范化为 RGB 三元组。green关键字对应rgb(0, 128, 0)——不是rgb(0, 255, 0)(后者是lime)。如果写background-color: lime或#00FF00,这条断言就会失败。
测试 3:id 必须写在 form 标签内,且只出现一次
assert.match(__helpers.removeHtmlComments(code), /<form.*cat-photo-form.*>/gi);
assert.lengthOf(__helpers.removeHtmlComments(code).match(/<form.*cat-photo-form.*>/gi), 1)
第一条用正则确认 cat-photo-form 出现在 <form ...> 开标签里(防止有人把 id 写到别的元素上);第二条用 lengthOf 限定匹配数恰好为 1,从代码层面落实了"id 唯一"这一最佳实践。
测试 4:form 不得带 class 或 style 属性
assert.notMatch(__helpers.removeHtmlComments(code), /<form.*style.*>/gi);
assert.notMatch(__helpers.removeHtmlComments(code), /<form.*class.*>/gi);
这两条负向断言封死了"走捷径"的路线:不允许用内联 style="background-color: green" 或新增一个 class 来绕过 id 选择器练习。__helpers.removeHtmlComments 会先剥离 HTML 注释,防止用注释技巧干扰正则匹配。
从仓库源码看:挑战文档如何变成可运行的课程数据
以上 --description--、--hints--、--seed--、--solutions-- 等标记段落,是 freeCodeCamp 课程仓库的 challenge 文件格式约定。从源码结构看,这些 Markdown 段会被解析管线加工为结构化的 challenge JSON:
- 种子代码的提取逻辑在 add-seed.js 中:该插件先定位
# --seed--段落,再要求其内部必须包含## --seed-contents--小节(缺失则抛出## --seed-contents-- must appear in # --seed-- sections错误),随后把每个语言的文件内容写入file.data.challengeFiles;文件中还定义了--fcc-editable-region--可编辑区域标记的查找与校验逻辑(最多两处标记,且必须成对闭合)。本挑战的 seed 未使用可编辑区域标记,整份 HTML 均可编辑。 - 解析后的字段约束由 Joi 校验,见 challenge-schema.js:其中
tests被定义为{ id, text, testString }对象数组(testString即--hints--中每段js代码块),title、challengeType(本挑战为 0,普通编码挑战)、helpCategory等均为必填。--hints--里每条提示的说明文字对应text,断言代码对应testString,这解释了为什么提示与判题代码是成对出现的。 - 块级顺序与布局则由 basic-css.json 的
challengeOrder数组和blockLayout: "legacy-challenge-list"决定,即该模块以传统挑战列表形式呈现给学习者。
这套"Markdown 挑战文档 → 解析插件 → Joi 校验 → 课程数据"的链路,保证了本挑战的 seed 代码、测试断言与 solution 在发布前就是可执行、可复核的事实数据,而非静态文本。
id 优先级的延伸验证
文档正文预告了"id 比 class 优先级更高",仓库中同一模块的 Override Class Declarations by Styling ID Attributes 挑战对此做了实证式教学:其 seed 中 h1 同时挂有 .pink-text 和 .blue-text 两个 class(按声明顺序 blue-text 生效),而 solution 在保留两个 class 的同时加上 id="orange-text" 与 #orange-text { color: orange; } 规则,验收测试则要求计算颜色为 rgb(255, 165, 0) 的 orange。该挑战说明还特别强调:无论 #orange-text 规则写在 class 规则之上还是之下,id 声明都必然胜出——因为 id 的选择器优先级高于 class,声明位置不再起决定作用。这也与本文档"if both are applied to the same element and have conflicting styles, the styles of the id will be applied"的表述完全对应。
小结
围绕 Use an id Attribute to Style an Element 这一挑战,可以提炼出四个要点:
- 语法:
<style>内引用 id 选择器一律用#前缀,如#cat-photo-form { background-color: green; };引用 class 则用.前缀。 - 约束:id 不可复用,一个页面上同一 id 只能出现一次;本挑战用"正则匹配计数恰好为 1"的测试把这条最佳实践变成了硬性验收条件。
- 优先级:id 的特异性高于 class,冲突时 id 胜出,且与声明顺序无关——这一点在同模块的覆盖类挑战中被再次验证。
- 工程链路:挑战文档中的 seed、hints(即判题断言)与 solutions 段落,经 challenge-parser 插件 解析、由 challenge-schema.js 的 Joi 规则校验后,成为交付给学习者的可运行课程数据。
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 StartedRust0625
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