首页
/ 用 id 选择器为单个元素定制样式:freeCodeCamp basic-css 课程实战解析

用 id 选择器为单个元素定制样式:freeCodeCamp basic-css 课程实战解析

2026-09-06 17:08:19作者:鲍丁臣Ursa

本篇以 freeCodeCamp 课程仓库中 basic-css 模块的挑战文档 Use an id Attribute to Style an Element 为主体,讲清 CSS id 选择器的语法、idclass 在选择优先级上的差异,以及该挑战在 CatPhotoApp 项目中的完整实操流程(含种子代码、验收测试与参考答案)。读完后你将掌握:如何用 # 前缀精确选中并样式化页面上唯一的元素、理解 id 高优先级的机制,以及如何从仓库源码层面理解挑战文件是如何被解析、校验和交付给在线编辑器的。

挑战在 basic-css 模块中的位置

basic-css 模块的完整挑战顺序定义在 basic-css.json 中。本挑战的 id 为 bad87dee1348bd9aede07836,标题为 "Use an id Attribute to Style an Element",位于模块第 15 位:

从这种编排顺序看,课程先把"设置 id"和"用 id 写样式"拆成两步,再在后面用"覆盖"类挑战(如 "Prioritize One Style Over Another")收尾,形成一个关于 CSS 选择器优先级的完整闭环。

核心概念:id 属性与 id 选择器

挑战文档的 --description-- 部分给出了三条核心知识,全部是理解本主题的关键:

  1. id 可以像 class 一样被 CSS 选中并样式化。区别在于 class 可以复用在多个元素上,而 id 不可复用,应只应用于一个元素。文档原文:

    However, an id is not reusable and should only be applied to one element.

  2. id 的 specificity(特异性/重要性)高于 class。如果同一个元素上同时存在 id 样式和 class 样式且声明了冲突的属性,浏览器会应用 id 一侧的样式。这为后续 "Override Class Declarations by Styling ID Attributes" 挑战埋下伏笔。

  3. 选择器前缀规则:在 <style> 元素内部,引用 class 永远在名字前加 .,引用 id 永远在名字前加 #。文档给出的示例是:假设页面上有一个 idcat-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 不得添加 classstyle 属性——样式只能通过 <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 代码块),titlechallengeType(本挑战为 0,普通编码挑战)、helpCategory 等均为必填。--hints-- 里每条提示的说明文字对应 text,断言代码对应 testString,这解释了为什么提示与判题代码是成对出现的。
  • 块级顺序与布局则由 basic-css.jsonchallengeOrder 数组和 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 这一挑战,可以提炼出四个要点:

  1. 语法<style> 内引用 id 选择器一律用 # 前缀,如 #cat-photo-form { background-color: green; };引用 class 则用 . 前缀。
  2. 约束:id 不可复用,一个页面上同一 id 只能出现一次;本挑战用"正则匹配计数恰好为 1"的测试把这条最佳实践变成了硬性验收条件。
  3. 优先级:id 的特异性高于 class,冲突时 id 胜出,且与声明顺序无关——这一点在同模块的覆盖类挑战中被再次验证。
  4. 工程链路:挑战文档中的 seed、hints(即判题断言)与 solutions 段落,经 challenge-parser 插件 解析、由 challenge-schema.js 的 Joi 规则校验后,成为交付给学习者的可运行课程数据。
登录后查看全文
热门项目推荐
相关项目推荐