首页
/ freeCodeCamp 课程源码解析:Inherit CSS Variables——在 :root 定义 CSS 变量并理解继承机制

freeCodeCamp 课程源码解析:Inherit CSS Variables——在 :root 定义 CSS 变量并理解继承机制

2026-09-06 16:49:53作者:温玫谨Lighthearted

本文以 freeCodeCamp 响应式网页设计(Responsive Web Design)课程中 "Inherit CSS Variables"(继承 CSS 变量)练习挑战为研究对象。该挑战文件位于课程数据目录 curriculum/challenges/english/blocks/basic-css/ 下,属于 basic-css 课程块中"CSS 变量(Custom Properties)"教学序列的一环。读完本文,你将掌握:CSS 变量为何可以被后代元素"自动"使用、:root 伪类选择器的作用、var() 回退值与变量继承的配合关系,以及该练习在仓库中如何通过种子代码(seed)与正则断言(assert)完成自动判题。

课程定位:这道挑战处于哪个知识点序列

挑战文件 5a9d7295424fe3d0e10cad14.md 的 YAML 元数据声明了它的基本属性:

id: 5a9d7295424fe3d0e10cad14
title: Inherit CSS Variables
challengeType: 0
videoUrl: 'https://scrimba.com/c/cyLZZhZ'
forumTopicId: 301088
dashedName: inherit-css-variables

challengeType: 0 表示这是一道"修改代码"型练习;dashedName 用于生成论坛与路由标识。

课程块的组织结构定义在 basic-css.json 中。按 challengeOrder 数组的顺序,CSS 变量这一教学序列依次是:

顺序 挑战标题 文件
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

可以看到,本篇是承上启下的关键一步:前序挑战刚教会"定义变量 → 用 var() 取用变量 → 附加回退值",本篇要求把变量上移到 :root 验证继承行为,随后的两道挑战则进一步演示在局部作用域覆盖变量值。该课程块同时被多个超块引用,例如 responsive-web-design.jsonbasic-css 列为第二个 block,而 responsive-web-design-v9.json 中还出现了 review-basic-cssquiz-basic-css 等复习与测验单元,说明 CSS 变量相关知识点在课程体系中被当作需要巩固的核心内容。

挑战讲解:CSS 变量是继承的,就像普通属性一样

挑战的 --description-- 段落是本题的核心讲解,原文含义如下:

  • 变量的作用域是声明它的那个选择器及其所有后代。当你创建(声明)一个 CSS 变量时,它在你声明它的选择器内部可用,并且在该选择器的任何后代元素中也可用。原因就是 CSS 变量与普通属性一样,是**可以被继承(inherited)**的。
  • 要利用这种继承,CSS 变量常被定义在 :root 元素上:root 是一个伪类选择器,匹配文档的根元素——通常就是 html 元素。把变量创建在 :root 中,它们就是全局可用的,样式表中的任何其他选择器都能访问到。

用一张继承链的示意来理解(html:root 匹配的根元素):

:root (html)                 ← 在 :root 声明 --penguin-belly: pink
└── body                     ← 继承:可用 var(--penguin-belly)
    └── div.penguin
        └── div.right-cheek  ← 继承:可用 var(--penguin-belly)
        └── div.belly        ← 继承:可用 var(--penguin-belly)
        └── ...(所有后代均可继承)

指令(--instructions--)要求::root 选择器中定义一个名为 --penguin-belly 的变量,值为 pink。完成后可以看到该变量被继承,所有使用它(var(--penguin-belly, ...))的子元素都会获得粉色背景。

种子代码(seed)全景:题目给了你什么

挑战文件 --seed-contents-- 段落内是学员在编辑器里看到的初始 HTML。题目在 :root 块里留出了作答区(两条 /* Only change code ... */ 注释之间),其余 CSS 与 DOM 是只读背景。完整种子代码如下:

<style>
  :root {
    /* Only change code below this line */

    /* Only change code above this line */
  }

  body {
    background: var(--penguin-belly, #c6faf1);
  }

  .penguin {
    --penguin-skin: gray;
    --penguin-beak: orange;
    position: relative;
    margin: auto;
    display: block;
    margin-top: 5%;
    width: 300px;
    height: 300px;
  }

  .right-cheek {
    top: 15%;
    left: 35%;
    background: var(--penguin-belly, white);
    width: 60%;
    height: 70%;
    border-radius: 70% 70% 60% 60%;
  }

  .left-cheek {
    top: 15%;
    left: 5%;
    background: var(--penguin-belly, white);
    width: 60%;
    height: 70%;
    border-radius: 70% 70% 60% 60%;
  }

  .belly {
    top: 60%;
    left: 2.5%;
    background: var(--penguin-belly, white);
    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>

阅读这份种子代码,能看出三个为本题服务的精心设计:

  1. :root 是空壳:题目唯一允许修改的就是 :root { ... } 内部,作答区位于两条注释之间。
  2. 四个选择器已经"预留"了变量引用body.right-cheek.left-cheek.belly 的背景全部写成 var(--penguin-belly, 回退色),回退色分别是 #c6faf1(青绿色页面背景)和 white。在变量尚未声明时,浏览器找不到 --penguin-belly,于是回退值生效——页面呈现青绿色背景、企鹅脸颊与腹部为白色。
  3. 对照变量已就位.penguin 类中声明了 --penguin-skin: gray;--penguin-beak: orange;,它们只服务于企鹅自身部件(头、脚、喙),与本题要求的"全局变量"形成对照,让学员直观体会"局部声明"与":root 全局声明"两种作用域。

参考答案:一行代码触发全页面变色

挑战文件 --solutions-- 段落给出的官方答案是:

<style>:root {--penguin-belly: pink;}</style>

展开写即:

:root {
  --penguin-belly: pink;
}

声明生效后的连锁反应是:

  • body 的背景从回退值 #c6faf1(青绿)变为 pink
  • .right-cheek.left-cheek.belly 三处从回退值 white 变为 pink
  • 使用 --penguin-skin--penguin-beak 的部件不受影响,仍为 grayorange

这正是题目描述的"变量被继承、所有使用它的子元素都获得粉色背景"的现象。它同时演示了两个机制的叠加:变量沿 DOM 树向下继承htmlbody → 各部件 div),以及 var() 回退值只在变量缺失时兜底——变量一旦在祖先链上任一层级可用,回退值就不再出现。

自动判题逻辑:断言如何验证你的答案

挑战文件 --hints-- 段落中,每条提示都对应一条正则断言。本题的判题代码如下:

assert.match(
  __helpers.removeCssComments(code),
  /:root\s*?{[\s\S]*--penguin-belly\s*?:\s*?pink\s*?;[\s\S]*}/gi
);

从源码结构看,这条断言的校验链条是:

  1. __helpers.removeCssComments(code) 先剥离 CSS 注释,避免 /* Only change code ... */ 干扰匹配;
  2. 正则 /:root\s*?{[\s\S]*--penguin-belly\s*?:\s*?pink\s*?;[\s\S]*}/gi 依次要求:存在 :root 选择器及其花括号,块内出现自定义属性声明 --penguin-belly,赋值运算符 : 两侧允许任意空白,值为 pink 且以分号结束;
  3. g 标志支持多处匹配,i 标志使其对 PINK 等大小写变体同样通过。

值得注意的是断言并不要求学员在 :root 中写满空白或注释,也不校验其他选择器——判题只锚定":root 内声明了 --penguin-belly: pink;"这一个事实,与讲解段落的知识点严格对齐。

延伸阅读:与相邻挑战的机制对照

理解本篇后,仓库内相邻的两道挑战是很好的机制对照:

  • 回退值(Attach a Fallback value to a CSS Variable:其讲解明确指出,回退值"不是用来提升浏览器兼容性的,在 IE 上不生效",而是让浏览器在找不到变量(如变量名拼写错误 --pengiun-skin)时有值可显示,"这对调试很有用"。本篇种子代码中 var(--penguin-belly, white) 的形式正是该机制的应用场景。
  • 局部覆盖(Change a variable for a specific area:该挑战的讲解说明——"当你在 :root 创建变量时,它为整页设置了该变量的值;你可以通过在某个具体选择器中再次设置来覆盖它",其种子代码正是在 :root 中声明 --penguin-skin: gray; --penguin-belly: pink; --penguin-beak: orange; 之后,要求把 --penguin-belly.penguin 类中重新赋值为 white。它与本篇首尾相接:先建立"根级全局变量 + 继承",再引入"局部重声明遮蔽继承值"。

两道挑战与本篇共同构成完整的 CSS 变量作用域知识链::root 全局声明 → 继承到任意后代 → var() 取用带回退 → 局部重声明覆盖继承值。

原理小结

从本题涉及到的 CSS 行为可以归纳出三条规则:

  1. :root 匹配文档根元素(通常是 html),在其上声明的自定义属性对整棵文档树"可见"——这是 CSS 变量最典型的"设计令牌(design tokens)"落点;
  2. 自定义属性默认参与继承,与普通颜色、字体类属性一样沿 DOM 树向下传递,因此子元素无需重新声明即可通过 var() 读取;
  3. var(变量名, 回退值) 的第二参数只是"变量不可用时的兜底",并非兼容性方案。变量只要在继承链上存在,回退值即被忽略。

以上行为均可直接在本题的交互环境中验证:修改 :root 块、观察 body 与企鹅脸颊/腹部颜色随继承链同步切换,即可对"变量继承"形成可操作的直觉。

相关路径索引

内容 仓库相对路径
本篇挑战源文件(讲解、种子、断言、答案) curriculum/challenges/english/blocks/basic-css/5a9d7295424fe3d0e10cad14.md
课程块结构与挑战顺序 curriculum/structure/blocks/basic-css.json
回退值挑战(前序) curriculum/challenges/english/blocks/basic-css/5a9d7286424fe3d0e10cad13.md
局部覆盖挑战(后序) curriculum/challenges/english/blocks/basic-css/5a9d72a1424fe3d0e10cad15.md
媒体查询改变量挑战(序列收尾) curriculum/challenges/english/blocks/basic-css/5a9d72ad424fe3d0e10cad16.md
超块引用(含 quiz/review 单元) curriculum/structure/superblocks/responsive-web-design-v9.json
登录后查看全文
热门项目推荐
相关项目推荐