freeCodeCamp Basic CSS:Change a variable for a specific area —— 在特定选择器中局部覆盖 CSS 变量
本文基于 freeCodeCamp 课程仓库中 basic-css(基础 CSS)模块的练习题 Change a variable for a specific area 展开。该练习的主题是:在 :root 中定义的 CSS 自定义属性(CSS Variables)会作用于整个页面,而你可以在某个具体选择器内重新声明同名变量,实现"局部覆盖"。读完本文,你将完整获得这套企鹅(penguin)CSS 艺术练习的原始代码与参考答案,理解变量继承、局部重写与 var() 回退值三者之间的配合方式,并了解该练习的自动判题逻辑是如何用正则表达式验证你的写法的。
练习定位:CSS Variables 系列中的"局部覆盖"环节
该练习(frontmatter 中 id 为 5a9d72a1424fe3d0e10cad15,dashedName 为 change-a-variable-for-a-specific-area,challengeType: 0 表示纯代码类挑战)位于 basic-css 模块的 CSS 变量教学序列中。从 basic-css.json 的 block 结构文件可以确认,这一序列的标题依次为:
| 顺序 | 练习标题 | 源文件 |
|---|---|---|
| 1 | Use CSS Variables to change several elements at once | 5a9d725e424fe3d0e10cad10.md |
| 2 | Create a custom CSS Variable | 5a9d726c424fe3d0e10cad11.md |
| 3 | Use a custom CSS Variable | 5a9d727a424fe3d0e10cad12.md |
| 4 | Attach a Fallback value to a CSS Variable | 5a9d7286424fe3d0e10cad13.md |
| 5 | Inherit CSS Variables | 5a9d7295424fe3d0e10cad14.md |
| 6 | Change a variable for a specific area(本文) | 5a9d72a1424fe3d0e10cad15.md |
| 7 | Use a media query to change a variable | 5a9d72ad424fe3d0e10cad16.md |
这个序列的教学递进很清晰:先学会"创建变量、用变量、带回退值",再理解":root 中定义的变量可被后代继承",然后进入本文的主题——在继承的基础上,于某个具体区域重新赋值,最后以媒体查询中的变量重定义收尾。整条链路都围绕同一个"企鹅"视觉案例(纯 CSS div + border-radius 拼成的企鹅图)展开,便于观察变量值变化对多个元素背景的即时影响。
核心概念:全局变量与局部覆盖
原文档给出的描述只有两句,但浓缩了本练习的全部原理:
当你在
:root中创建变量时,它们会把该变量的值设置为整个页面的值(When you create your variables in:rootthey will set the value of that variable for the whole page)。随后你可以在某个具体选择器内再次设置这些变量,从而覆盖它们(You can then overwrite these variables by setting them again within a specific selector)。
这句话的前提知识来自前一节 Inherit CSS Variables 练习的讲解:CSS 变量和普通属性一样是可继承的——变量在创建它的那个选择器内可用,并且在其所有后代元素中可用,因此变量常被集中定义在 :root(匹配文档根元素 html 的伪类选择器)中以获得全局作用域。
由此可以推出本文的机制:
:root中的--penguin-belly: pink;使pink成为整页的默认值,任何通过var(--penguin-belly, ...)引用它的地方都会取到pink;- 如果在
.penguin选择器里再次写--penguin-belly: white;,那么.penguin元素本身及其所有后代从该元素读取--penguin-belly时取到的就是white; - 由于变量是通过继承"流入"后代的,只需重写一次变量,所有引用它的属性(
background等)自动随之改变,无需逐个修改每条声明。
这正是 CSS 变量相对传统写法(或 preprocessor 变量)的价值所在:变量是"运行期"的(由浏览器在渲染树中解析),因此可以在层叠中的任意层级被重新赋值,而不仅限于预处理器阶段的静态替换。
完整练习代码(seed 与参考答案)
以下是该练习在学员编辑器中的完整初始代码,与仓库中的 --seed-contents-- 部分完全一致。注意 body 背景、.right-cheek、.left-cheek、.belly 都通过 var(--penguin-belly, #c6faf1)(或 pink)引用该变量——这是观察"局部覆盖"效果的关键点。
<style>
:root {
--penguin-skin: gray;
--penguin-belly: pink;
--penguin-beak: orange;
}
body {
background: var(--penguin-belly, #c6faf1);
}
.penguin {
/* Only change code below this line */
/* Only change code above this line */
position: relative;
margin: auto;
display: block;
margin-top: 5%;
width: 300px;
height: 300px;
}
.right-cheek {
top: 15%;
left: 35%;
background: var(--penguin-belly, pink);
width: 60%;
height: 70%;
border-radius: 70% 70% 60% 60%;
}
.left-cheek {
top: 15%;
left: 5%;
background: var(--penguin-belly, pink);
width: 60%;
height: 70%;
border-radius: 70% 70% 60% 60%;
}
.belly {
top: 60%;
left: 2.5%;
background: var(--penguin-belly, pink);
width: 95%;
height: 100%;
border-radius: 120% 120% 100% 100%;
}
.penguin-top {
top: 10%;
left: 25%;
background: var(--penguin-skin, gray);
width: 50%;
height: 45%;
border-radius: 70% 70% 60% 60%;
}
.penguin-bottom {
top: 40%;
left: 23.5%;
background: var(--penguin-skin, gray);
width: 53%;
height: 45%;
border-radius: 70% 70% 100% 100%;
}
.right-hand {
top: 0%;
left: -5%;
background: var(--penguin-skin, gray);
width: 30%;
height: 60%;
border-radius: 30% 30% 120% 30%;
transform: rotate(45deg);
z-index: -1;
}
.left-hand {
top: 0%;
left: 75%;
background: var(--penguin-skin, gray);
width: 30%;
height: 60%;
border-radius: 30% 30% 30% 120%;
transform: rotate(-45deg);
z-index: -1;
}
.right-feet {
top: 85%;
left: 60%;
background: var(--penguin-beak, orange);
width: 15%;
height: 30%;
border-radius: 50% 50% 50% 50%;
transform: rotate(-80deg);
z-index: -2222;
}
.left-feet {
top: 85%;
left: 25%;
background: var(--penguin-beak, orange);
width: 15%;
height: 30%;
border-radius: 50% 50% 50% 50%;
transform: rotate(80deg);
z-index: -2222;
}
.right-eye {
top: 45%;
left: 60%;
background: black;
width: 15%;
height: 17%;
border-radius: 50%;
}
.left-eye {
top: 45%;
left: 25%;
background: black;
width: 15%;
height: 17%;
border-radius: 50%;
}
.sparkle {
top: 25%;
left: 15%;
background: white;
width: 35%;
height: 35%;
border-radius: 50%;
}
.blush-right {
top: 65%;
left: 15%;
background: pink;
width: 15%;
height: 10%;
border-radius: 50%;
}
.blush-left {
top: 65%;
left: 70%;
background: pink;
width: 15%;
height: 10%;
border-radius: 50%;
}
.beak-top {
top: 60%;
left: 40%;
background: var(--penguin-beak, orange);
width: 20%;
height: 10%;
border-radius: 50%;
}
.beak-bottom {
top: 65%;
left: 42%;
background: var(--penguin-beak, orange);
width: 16%;
height: 10%;
border-radius: 50%;
}
.penguin * {
position: absolute;
}
</style>
<div class="penguin">
<div class="penguin-bottom">
<div class="right-hand"></div>
<div class="left-hand"></div>
<div class="right-feet"></div>
<div class="left-feet"></div>
</div>
<div class="penguin-top">
<div class="right-cheek"></div>
<div class="left-cheek"></div>
<div class="belly"></div>
<div class="right-eye">
<div class="sparkle"></div>
</div>
<div class="left-eye">
<div class="sparkle"></div>
</div>
<div class="blush-right"></div>
<div class="blush-left"></div>
<div class="beak-top"></div>
<div class="beak-bottom"></div>
</div>
</div>
指令与参考答案
原文档的指令(--instructions--)为:在 penguin 类中,把 --penguin-belly 的值改为 white(Change the value of --penguin-belly to white in the penguin class)。
对应参考答案(--solutions-- 部分)极简,只有一行:
<style>
.penguin {--penguin-belly: white;}
</style>
在编辑器中,它应填入 .penguin 规则内两行标记注释(/* Only change code below this line */ 与 /* Only change code above this line */)之间,最终 .penguin 规则大致为:
.penguin {
--penguin-belly: white;
position: relative;
margin: auto;
display: block;
margin-top: 5%;
width: 300px;
height: 300px;
}
应用后的视觉效果是"分层变色"的:
body的背景仍引用var(--penguin-belly, #c6faf1)。由于<body>是.penguin的父级而非子级,它读取到的仍是:root中的pink,所以页面背景保持粉色;.right-cheek、.left-cheek、.belly都是.penguin的后代,它们读取到的--penguin-belly变为white,脸颊和腹部随之变白;--penguin-skin(灰色身体)与--penguin-beak(橙色嘴/脚)不受影响。
这一步恰好验证了"覆盖是作用域内的":同一个变量名在页面不同区域可以持有不同值,各区域取的是"距离最近且可见"的那次声明。
判题逻辑:两条正则断言说明了什么
该练习的 --hints-- 部分同时暴露了判题所用的两条断言,读它们能精确定义"正确答案的边界":
// 断言 1:.penguin 规则内必须把 --penguin-belly 重新赋值为 white
assert.match(__helpers.removeCssComments(code), /\.penguin\s*?{[\s\S]*--penguin-belly\s*?:\s*?white\s*?;[\s\S]*}/gi);
// 断言 2:整段代码中不得出现 background-color 声明
assert.match(__helpers.removeCssComments(code), /^((?!background-color\s*?:\s*?)[\s\S])*$/g);
两条断言合起来传达的教学意图非常明确:
- 正解路径:必须通过"重新声明变量"这一机制完成变色,而不是绕过变量直接改属性。断言 1 的正则要求
--penguin-belly: white;出现在.penguin { ... }花括号之内([\s\S]*允许前后存在其他声明)。 - 排除错误路径:断言 2 用否定前瞻禁止出现任何
background-color:声明,防止学员直接给.belly等元素写死background-color: white来"蒙混过关"——那样虽然视觉结果相同,但没有练习到变量覆盖这一知识点。
另外,断言前都会先调用 __helpers.removeCssComments(code) 去掉 CSS 注释,因此标记注释行本身不影响判题。
原理小结与实践要点
结合本练习与同系列 Inherit CSS Variables、Attach a Fallback value to a CSS Variable 两篇讲解,可以把要点归纳为:
- 变量是继承属性:在
:root声明后,整页可见;在某个选择器(如.penguin)重新声明后,该选择器匹配的元素及其所有后代看到的新值都会覆盖祖先值。 - 改变量,不改属性:本例中
.right-cheek、.left-cheek、.belly三条background: var(--penguin-belly, pink);一行未动,颜色却因继承链上的值变化而改变。这也是系列第一节练习(Use CSS Variables to change several elements at once)所强调的"改一个值、变多处样式"的能力。 - 回退值仍然生效:即使变量在某处被重定义,
var()第二个参数(回退值)依旧只在"变量无效或未定义"时兜底。本例中.right-cheek等的回退值pink不会被触发,因为变量在继承链中始终有定义;而body处的回退值#c6faf1同理不会被触发——:root的pink已经生效。 - 覆盖范围由选择器决定:想让"局部"更小,可以把重定义写在更深层的选择器上(例如只改
.belly内的变量);想让"局部"更大,则写在更靠外层的规则中。从仓库源码结构看,freeCodeCamp 的判题器只校验.penguin花括号内的赋值,因此只要满足这一作用域要求即可通过。 - 不要写死颜色:如前所述,绕过变量直接声明
background-color会被断言 2 判为不通过,这在真实项目中同样是一个坏味道——它会破坏"一处定义、处处生效"的主题化能力。
后续延伸
完成本练习后,系列中的最后一篇 Use a media query to change a variable 会把同样的"重定义变量"手法移进 @media (max-width: 350px) 的 :root 中:在窄屏时重新赋值 --penguin-size: 200px 和 --penguin-skin: black,让整只企鹅随视口尺寸缩放、换色——这正是"变量 + 媒体查询"组合在响应式设计中的典型用法,也是本练习所铺垫知识的直接应用。
如果你希望通读整个基础 CSS 模块的练习编排,可以查看 block 结构文件 basic-css.json 获取各练习的标题与顺序;本练习在课程中的唯一标识(id: 5a9d72a1424fe3d0e10cad15)即对应仓库文件名 5a9d72a1424fe3d0e10cad15.md。
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