HSL 色彩模型与色调调整:解析 freeCodeCamp《Adjust the Hue of a Color》实战关卡
本篇基于 freeCodeCamp 开源课程仓库中的实战关卡 Adjust the Hue of a Color(applied-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>
任务要求:根据类名(green、cyan、blue),使用 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() 中的 s 与 l 正好对应这两个自由度——饱和度百分比控制灰色含量,亮度百分比控制白/黑含量。其参考答案将 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))——这正是"保留基色相、只调 s 和 l 生成同一色系的深浅变体"这一实用技巧的直接体现。
再往后一站是 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 渐变等后续关卡。
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 StartedRust0624
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