首页
/ freeCodeCamp Basic CSS:Change a variable for a specific area —— 在特定选择器中局部覆盖 CSS 变量

freeCodeCamp Basic CSS:Change a variable for a specific area —— 在特定选择器中局部覆盖 CSS 变量

2026-09-06 16:53:51作者:仰钰奇

本文基于 freeCodeCamp 课程仓库中 basic-css(基础 CSS)模块的练习题 Change a variable for a specific area 展开。该练习的主题是:在 :root 中定义的 CSS 自定义属性(CSS Variables)会作用于整个页面,而你可以在某个具体选择器内重新声明同名变量,实现"局部覆盖"。读完本文,你将完整获得这套企鹅(penguin)CSS 艺术练习的原始代码与参考答案,理解变量继承、局部重写与 var() 回退值三者之间的配合方式,并了解该练习的自动判题逻辑是如何用正则表达式验证你的写法的。

练习定位:CSS Variables 系列中的"局部覆盖"环节

该练习(frontmatter 中 id5a9d72a1424fe3d0e10cad15dashedNamechange-a-variable-for-a-specific-areachallengeType: 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 :root they 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 的伪类选择器)中以获得全局作用域。

由此可以推出本文的机制:

  1. :root 中的 --penguin-belly: pink; 使 pink 成为整页的默认值,任何通过 var(--penguin-belly, ...) 引用它的地方都会取到 pink
  2. 如果在 .penguin 选择器里再次写 --penguin-belly: white;,那么 .penguin 元素本身及其所有后代从该元素读取 --penguin-belly 时取到的就是 white
  3. 由于变量是通过继承"流入"后代的,只需重写一次变量,所有引用它的属性(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. 正解路径:必须通过"重新声明变量"这一机制完成变色,而不是绕过变量直接改属性。断言 1 的正则要求 --penguin-belly: white; 出现在 .penguin { ... } 花括号之内([\s\S]* 允许前后存在其他声明)。
  2. 排除错误路径:断言 2 用否定前瞻禁止出现任何 background-color: 声明,防止学员直接给 .belly 等元素写死 background-color: white 来"蒙混过关"——那样虽然视觉结果相同,但没有练习到变量覆盖这一知识点。

另外,断言前都会先调用 __helpers.removeCssComments(code) 去掉 CSS 注释,因此标记注释行本身不影响判题。

原理小结与实践要点

结合本练习与同系列 Inherit CSS VariablesAttach 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 同理不会被触发——:rootpink 已经生效。
  • 覆盖范围由选择器决定:想让"局部"更小,可以把重定义写在更深层的选择器上(例如只改 .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

登录后查看全文
热门项目推荐
相关项目推荐