首页
/ HSL 色彩模型与色调调整:解析 freeCodeCamp《Adjust the Hue of a Color》实战关卡

HSL 色彩模型与色调调整:解析 freeCodeCamp《Adjust the Hue of a Color》实战关卡

2026-09-06 13:29:31作者:俞予舒Fleming

本篇基于 freeCodeCamp 开源课程仓库中的实战关卡 Adjust the Hue of a Colorapplied-visual-design 模块)展开,讲解 CSS3 hsl() 颜色函数的三大参数——色相(Hue)、饱和度(Saturation)与亮度(Lightness)的取值规则与相互关系,并完整还原关卡的初始代码、解题步骤与自动化测试判定逻辑。读完后你将掌握如何用 hsl() 精确描述任意颜色、快速计算色相角度,并理解浏览器如何将 HSL 值归一化为 rgb() 用于样式校验。

关卡在课程中的定位

applied-visual-design 是 Responsive Web Design 认证中的一个知识块(block),在 responsive-web-design.json 中列于 basic-css 之后,是学员掌握基础 CSS 属性后进入的视觉设计专题。本关卡的 ID 为 587d78a4367417b2b2512ad4,标题 "Adjust the Hue of a Color",在 applied-visual-design.json 的课程顺序中排在该模块色彩理论小节的位置,紧邻前置关卡 Learn about Complementary Colors(互补色)与后置关卡 Adjust the Tone of a Color(色调调整),共同构成"颜色理论 → HSL 表示 → 色调变换 → 渐变"的递进学习路径。

颜色的三个基本特征

颜色的特征不止一种描述方式。CSS3 引入 hsl() 函数,作为十六进制与 rgb() 之外的第三种选色方式,允许开发者直接声明颜色的三个核心特征:色相、饱和度和亮度。

色相(Hue) 就是人们通常所说的"颜色"本身。如果把颜色想象成一条从左端的红色、经过中端的绿色、到右端的蓝色的光谱,色相就是某个颜色在这条线上的位置。而在 hsl() 中,色相采用的不是光谱而是**色轮(color wheel)**模型:颜色排列在一个圆环上,色相用角度表示,取值范围是 0 到 360

饱和度(Saturation) 表示颜色中灰色的含量。完全饱和的颜色不含任何灰色;饱和度最低的颜色则几乎完全是灰色。它以百分比表示,100% 表示完全饱和

亮度(Lightness) 表示颜色中白色或黑色的含量。它以百分比表示,取值范围从 0%(纯黑)100%(纯白),其中 50% 表示正常(原色)状态

全饱和、正常亮度下的基色 HSL 对照表

以下是原文档给出的经典对照表:当饱和度为 100%、亮度为 50% 时,六种基色与色相角度的对应关系:

颜色 HSL 值
红色(red) hsl(0, 100%, 50%)
黄色(yellow) hsl(60, 100%, 50%)
绿色(green) hsl(120, 100%, 50%)
青色(cyan) hsl(180, 100%, 50%)
蓝色(blue) hsl(240, 100%, 50%)
品红(magenta) hsl(300, 100%, 50%)

从这张表可以归纳出一个实用规律:色相环上相邻基色恰好间隔 60°,相对(互补)的基色间隔 180°。这一规律与前置关卡 Learn about Complementary Colors 中的结论一致——在色轮上处于对角位置的颜色互为互补色,例如 red (#FF0000) 与 cyan (#00FFFF)、green (#00FF00) 与 magenta (#FF00FF)、blue (#0000FF) 与 yellow (#FFFF00)。用 HSL 视角看,互补色的色相角正好相差 180°,例如绿色 120° 的互补色品红为 120° + 180° = 300°。这也解释了为何现代设计采用加色 RGB 模型与减色 CMY(K) 模型,而非传统教学中过时的 RYB 色轮。

实战任务:用 hsl() 设置三个 div 的背景色

关卡给出了如下初始代码(seed),三个 div 的背景色均为黑色 #000000,需要改写:

<style>
  body {
    background-color: #FFFFFF;
  }

  .green {
    background-color: #000000;
  }

  .cyan {
    background-color: #000000;
  }

  .blue {
    background-color: #000000;
  }

  div {
    display: inline-block;
    height: 100px;
    width: 100px;
  }
</style>

<div class="green"></div>
<div class="cyan"></div>
<div class="blue"></div>

任务要求:根据类名(greencyanblue),使用 hsl() 函数修改每个 div 元素的 background-color,且三个颜色都必须满足**完全饱和(100%)且正常亮度(50%)**两个条件。

对照上表直接取色相角度即可得到标准解:

<style>
  body {
    background-color: #FFFFFF;
  }

  .green {
    background-color: hsl(120, 100%, 50%);
  }

  .cyan {
    background-color: hsl(180, 100%, 50%);
  }

  .blue {
    background-color: hsl(240, 100%, 50%);
  }

  div {
    display: inline-block;
    height: 100px;
    width: 100px;
  }
</style>
<div class="green"></div>
<div class="cyan"></div>
<div class="blue"></div>

三行关键改动就是把 #000000 替换为 hsl(120, 100%, 50%)hsl(180, 100%, 50%)hsl(240, 100%, 50%)。由于饱和度锁定在 100%、亮度锁定在 50%,颜色完全由色相角度唯一确定,因此这道题本质上就是查表填色相角度

自动化测试如何判定你的答案

关卡内置了两层断言,分别验证"写法"与"渲染结果",这是 freeCodeCamp 课程测试的典型模式,值得单独分析。

第一层:源码正则检查——确认你确实使用了 hsl() 函数(而不是直接写十六进制):

assert.match(code, /\.green\s*?{\s*?background-color\s*:\s*?hsl/gi);
assert.match(code, /\.cyan\s*?{\s*?background-color\s*:\s*?hsl/gi);
assert.match(code, /\.blue\s*?{\s*?background-color\s*:\s*?hsl/gi);

这三个正则允许属性名与 hsl 之间有任意空白,但不允许你写成 hsla() 之外的其它函数形式。

第二层:计算样式检查——通过 DOM 读取实际渲染后的颜色:

const greenElement = document.querySelector(".green");
const greenStyle = window.getComputedStyle(greenElement);
assert.equal(greenStyle?.backgroundColor, 'rgb(0, 255, 0)');

const cyanElement = document.querySelector(".cyan");
const cyanStyle = window.getComputedStyle(cyanElement);
assert.equal(cyanStyle?.backgroundColor, 'rgb(0, 255, 255)');

const blueElement = document.querySelector(".blue");
const blueStyle = window.getComputedStyle(blueElement);
assert.equal(blueStyle?.backgroundColor, 'rgb(0, 0, 255)');

这里有一个容易踩坑的细节:断言的期望值全部是 rgb() 字符串rgb(0, 255, 0)rgb(0, 255, 255)rgb(0, 0, 255)),而不是 hsl() 字符串。从源码结构看,这对应浏览器的标准行为——window.getComputedStyle() 返回的是归一化后的计算值,HSL 颜色在计算阶段被解析并转换为等价的原色 rgb() 表示。因此:

  • hsl(120, 100%, 50%) 计算后等于 rgb(0, 255, 0)(纯绿);
  • hsl(180, 100%, 50%) 等于 rgb(0, 255, 255)(纯青);
  • hsl(240, 100%, 50%) 等于 rgb(0, 0, 255)(纯蓝)。

这意味着写测试时只需知道"HSL 与 RGB 的等价换算",而学员提交端只要 HSL 三个参数正确,浏览器渲染出的计算样式自然匹配。若色相角度写错(如绿色写成 hsl(120, 100%, 60%)),归一化后的 rgb 值会偏离期望值导致断言失败——这反过来说明测试同时锁定了饱和度与亮度两个参数。

由色相到色调:关卡前后的延伸应用

理解本关卡后,可以顺势读懂紧随其后的 Adjust the Tone of a Color 关卡(587d78a4367417b2b2512ad5.md)。该关卡用同一套 hsl() 语法演示色调(tone)与阴影(shade)调整:混合白色得到色调(tint),加黑色得到阴影(shade),加灰色则产生中间色调。hsl() 中的 sl 正好对应这两个自由度——饱和度百分比控制灰色含量,亮度百分比控制白/黑含量。其参考答案将 nav 元素设为:

nav {
  background-color: hsl(180, 80%, 25%);
}

该关卡的断言同样体现了 HSL 到 RGB 的归一化换算,且给出了精确的期望值注释:

// Computed style of hsl(180, 80%, 25%) results in rgb(13,115,115)
assert.equal(
  new __helpers.CSSHelp(document).getStyle('nav').getPropVal('background-color', true),
  'rgb(13,115,115)'
)

可以看到,同一个青色色相 180°,把饱和度从 100% 降到 80%、亮度从 50% 降到 25% 后,颜色从亮青变为深灰青(rgb(13, 115, 115))——这正是"保留基色相、只调 sl 生成同一色系的深浅变体"这一实用技巧的直接体现。

再往后一站是 Create a Gradual CSS Linear Gradient 关卡(587d78a5367417b2b2512ad6.md),其中 linear-gradient() 的色标参数同样可以接受 hsl() 颜色值,与前两个关卡形成完整的衔接。

小结

  • hsl(h, s%, l%) 以色轮角度、饱和度百分比、亮度百分比三要素精确描述颜色;全饱和正常亮度的基色色相角为 0°/60°/120°/180°/240°/300°,间隔恒为 60°,互补色相差 180°。
  • 本关卡的解题核心是查表替换三个 background-color 声明,标准答案即 hsl(120, 100%, 50%)hsl(180, 100%, 50%)hsl(240, 100%, 50%)
  • 测试先以正则强制使用 hsl() 写法,再经 getComputedStyle() 按归一化后的 rgb() 值校验渲染结果,因此 HSL 三个参数缺一不可、必须全部正确。
  • 掌握"HSL 与 RGB 的等价换算"后,即可平滑进入色调调整(hsl(180, 80%, 25%)rgb(13,115,115))与 CSS 渐变等后续关卡。
登录后查看全文
热门项目推荐
相关项目推荐