freeCodeCamp 响应式 Web 设计:用 CSS hsl() 调整颜色色调(Tone)的完整实战
本篇基于 freeCodeCamp 官方课程中的挑战文档 Adjust the Tone of a Color 展开。你将学到 CSS hsl() 函数中饱和度(saturation)与明度(lightness)两个参数如何决定颜色的色调变化(tint / shade / tone),并完整复现该课程中“给 nav 元素设置一个更暗青色背景”的实战题目:从初始代码、解题思路,到测试断言 rgb(13,115,115) 的数值验证过程。
该挑战在 freeCodeCamp 课程中的位置
这道题属于 freeCodeCamp 课程结构中的 Responsive Web Design 超模块(superblock)下的 Applied Visual Design 模块:
- 超模块定义见 responsive-web-design.json,其中
applied-visual-design排在basic-html-and-html5、basic-css之后,即学习者已经掌握 HTML 与 CSS 基础后进入的“视觉设计应用”阶段; - 模块内的挑战顺序定义在 applied-visual-design.json,本挑战
587d78a4367417b2b2512ad5位于“Learn about Complementary Colors”“Adjust the Hue of a Color”等颜色理论题之后、“Create a Gradual CSS Linear Gradient”之前,课程编排上正是“先懂色相 → 再调色调 → 最后做渐变”的递进路线; - 前序挑战 Adjust the Hue of a Color 已经介绍了
hsl()的三个参数与常用色相取值表,本文在“参数速查”一节中会引用该背景知识。
背景知识:hsl() 的三个参数
颜色具有色相(hue)、饱和度(saturation)、明度(lightness)三个特征。CSS3 引入 hsl() 函数,允许直接通过这三个特征指定颜色:
- 色相(h):即通常所说的“颜色”本身,取值 0–360,表示颜色在色轮上的角度。
- 饱和度(s):颜色中灰色分量的多少,以百分比表示,100% 为完全饱和。
- 明度(l):颜色中白色或黑色分量的多少,取值 0%(黑)到 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%) |
核心概念:用 hsl() 调整色调(Tone)
hsl() 让调整颜色色调变得很直观,其混色规则可以归纳为三条:
- Tint(浅色):给纯色(pure hue)混入白色,得到该颜色的浅色版本;
- Shade(暗色):给纯色混入黑色,得到该颜色的暗色版本;
- Tone(色调):给纯色混入灰色,或者同时做 tinting 和 shading,即得到该颜色的一个色调变化。
映射到 hsl() 的参数上(原文强调,hsl() 中的 “s” 和 “l” 分别代表 saturation 和 lightness):
- 饱和度百分比(s) 改变颜色中的灰色量——越低越灰、越不鲜艳;
- 明度百分比(l) 决定颜色中白色或黑色的比例——低于 50% 相当于掺黑(趋向 shade),高于 50% 相当于掺白(趋向 tint)。
这套机制的实际用途是:当你看中某个基础色相(base hue)、又需要它的一系列深浅变化时(例如同一青色做页头、导航栏、按钮的不同层级),无需另外找色值,只要固定色相、调整 s 和 l 即可得到成套配色。
实战题目:让 nav 用同一青色相,但变成深色调
题目场景(完整继承自原文档)
所有元素的默认 background-color 是 transparent。示例中 nav 元素看起来有 cyan 背景,其实只是因为它后面的元素把 background-color 设成了 cyan(透明背景下透出底层颜色)。题目要求:给 nav 元素添加 background-color,使其与页头使用相同的 cyan 色相(180),但饱和度为 80%、明度为 25%,从而改变它的色调并做成暗色(shade)。
初始代码(seed)
课程提供的初始代码如下(来自文档 --seed-contents-- 部分):
<style>
header {
background-color: hsl(180, 90%, 35%);
color: #FFFFFF;
}
nav {
}
h1 {
text-indent: 10px;
padding-top: 10px;
}
nav ul {
margin: 0px;
padding: 5px 0px 5px 30px;
}
nav li {
display: inline;
margin-right: 20px;
}
a {
text-decoration: none;
color: inherit;
}
</style>
<header>
<h1>Cooking with FCC!</h1>
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Classes</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
注意两点:页头已经是 hsl(180, 90%, 35%)——色相 180(cyan)、饱和度 90%、明度 35%;nav 选择器是空的,等待你填入规则。
标准答案
nav {
background-color: hsl(180, 80%, 25%);
}
即保持色相 180 不变,把饱和度从 90% 降到 80%(掺入更多灰)、明度从 35% 降到 25%(掺入更多黑),得到一个比页头更深、更沉稳的同色系导航条。文档 --solutions-- 部分给出的完整解法就是仅在这一处补充该声明,其余样式与 seed 完全一致。
测试如何验证答案:computed style 与 rgb(13,115,115)
原档的提示(hints)部分是本题最硬核的验证逻辑。它并不检查你写的源代码文本,而是通过课程内置的样式辅助类 CSSHelp 读取浏览器计算后的样式(computed style):
// 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)'
)
从源码结构看,freeCodeCamp 客户端的挑战框架会在运行环境中向测试脚本注入 __helpers(例如 frame.ts 中专门为 CSSHelper 类处理框架自身样式注入的注释),getStyle('nav') 取得目标元素的样式集合,getPropVal('background-color', true) 取计算后的背景色并归一化为 rgb() 字符串。这带来两个重要结论:
- 等价写法都能通过:只要浏览器把值解析为
hsl(180, 80%, 25%)的计算结果,断言就能通过,因此用hsla()、省略写法或直接写等价的rgb(13,115,115)在计算结果层面也是等价的(但课程意图是练习hsl()语法); - rgb 值是浏览器把 HSL 转换后统一归一化的结果,与书写格式无关。
手动验证:hsl(180, 80%, 25%) 为什么等于 rgb(13,115,115)
按 HSL → RGB 标准算法可以手工核算这个断言值,确认题目数值无误:
- 归一化:L = 0.25,S = 0.8,色相角 H′ = 180/60 = 3;
- 计算色度 C = (1 − |2L − 1|) × S = (1 − 0.5) × 0.8 = 0.4;
- H′ = 3 落在 3~4 区间,预混合分量为 (0, X, C),其中 X = C = 0.4,得 (0, 0.4, 0.4);
- 加偏移 m = L − C/2 = 0.25 − 0.2 = 0.05,得 (0.05, 0.45, 0.45);
- 映射到 0–255:0.05×255 ≈ 12.75 → 13;0.45×255 ≈ 114.75 → 115。
结果正是 rgb(13,115,115),与测试断言完全一致。同理可用该方法核算页头的 hsl(180, 90%, 35%) ≈ rgb(9,170,170),可见导航条比页头明显更暗(明度 25% vs 35%)、更灰(饱和度 80% vs 90%),这正是“同色相下通过调 s/l 制造层级对比”的典型做法。
小结与延伸
hsl()的三个参数中,色相决定“是什么颜色”,饱和度决定“掺了多少灰”,明度决定“掺了多少白/黑”;固定色相、只动 s 和 l,就能产出一套协调的色调序列(tints、shades、tones)。- 判断当前效果时,应以浏览器计算后的
rgb()值为准,而不是源代码文本——这也是本课程测试断言rgb(13,115,115)的验证思路。 - 学完本节后,课程在 applied-visual-design.json 中的下一题是 Create a Gradual CSS Linear Gradient,会把这些
hsl()颜色值作为渐变的色标(color stop)组合起来,是本文配色思路的直接下游应用。
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 StartedRust0623
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