首页
/ freeCodeCamp 响应式 Web 设计课程实战:用 CSS box-shadow 为卡片元素添加多层阴影

freeCodeCamp 响应式 Web 设计课程实战:用 CSS box-shadow 为卡片元素添加多层阴影

2026-09-06 12:30:05作者:仰钰奇

本篇以 freeCodeCamp 官方课程中的一个实战练习题为主体,讲解 CSS box-shadow 属性的完整参数体系(offset-x、offset-y、blur-radius、spread-radius、color)、多阴影叠加的写法与取值含义,并展示该练习题在课程代码库中的原始定义、测试断言与参考实现。读完后,你既能掌握"为卡片元素添加双层半透明阴影"这一常见视觉设计的落地写法,也能了解 freeCodeCamp 课程文件从 Markdown 到可运行题目的完整解析流程。

课程定位:这道题在 freeCodeCamp 课程中的位置

该练习题的源文件是 587d781b367417b2b2512abe.md,从文件的 YAML frontmatter 可以看到其元数据:

id: 587d781b367417b2b2512abe
title: Add a box-shadow to a Card-like Element
challengeType: 0
forumTopicId: 301031
dashedName: add-a-box-shadow-to-a-card-like-element

它在课程体系中的挂载关系可以从两处仓库配置中确认:

  • 区块定义 applied-visual-design.json 中,该题以 challengeOrder 数组第 11 个条目(id 为 587d781b367417b2b2512abe)排在 "Adjust the Size of a Heading Element..." 之后、"Decrease the Opacity of an Element" 之前;
  • 超级区块定义 responsive-web-design.json 中,applied-visual-design 是 Responsive Web Design 课程的第 3 个 block(位于 basic-html-and-html5basic-css 之后,applied-accessibility 之前)。

也就是说,这道题属于"Applied Visual Design"(视觉设计应用)教学块:学员已经掌握了 text-alignwidth/heightbackground-colorfont-size 等基础视觉属性之后,开始学习用 box-shadow 等属性塑造元素的立体感。challengeType: 0 对应仓库中 challenge-types.ts 里的 html 类型(取值 0),该类型在 viewTypes 中映射为 classic 经典双栏编辑器视图,在 submitTypes 中映射为 tests——即提交时运行题目自带的测试断言来判定是否通过,这正是本文后文分析测试正则的依据。

核心知识点:box-shadow 的五个参数

题目正文(# --description-- 小节)给出了 box-shadow 属性的权威描述:该属性可以为一个元素应用一个或多个阴影,其取值按以下固定顺序给出:

参数 是否必需 含义
offset-x 必需 阴影相对元素在水平方向上偏移的距离
offset-y 必需 阴影相对元素在垂直方向上偏移的距离
blur-radius 可选 阴影的模糊半径,值越大阴影越柔和
spread-radius 可选 阴影的扩散半径,正值让阴影比元素更大
color 可选 阴影颜色

两个要点值得强调:

  1. 参数顺序不可调换offset-xoffset-y 必须是前两个值,blur-radiusspread-radiuscolor 依次排在其后,浏览器按位置解析而非按名称解析。
  2. 多阴影用逗号分隔。题目原文指出:"Multiple box-shadows can be created by using commas to separate properties of each box-shadow element." 每写一组完整的五元组、再跟一个逗号,就能叠加一层新阴影。

题目给出的标准示例正是"多层阴影 + 模糊 + 半透明黑色"的组合:

box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);

拆解这行 CSS 的两层阴影:

  • 第一层 0 10px 20px rgba(0,0,0,0.19):水平偏移 0、垂直下移 10px、模糊半径 20px、未指定 spread、颜色为透明度 0.19 的黑色。它构成元素下方一大片"远景"柔光阴影;
  • 第二层 0 6px 6px rgba(0,0,0,0.23):垂直下移 6px、模糊半径 6px、透明度 0.23。它更贴近元素本体,相当于"近景"接触阴影。

两层叠加后,卡片会呈现出有层次、有深度的悬浮效果:远处一层大而淡的模糊,近处一层小而略深的阴影。这里没有使用 spread-radius(第四位),题目特意提示它是可选参数——省略时浏览器按 0 处理。另外注意 rgba(0,0,0,0.19) 这类低透明度黑色是 Material Design 风格阴影的常用做法:用黑色加透明度代替纯 black,让阴影能与任何背景色自然融合。

题目要求与卡片种子代码

# --instructions-- 小节给出的任务说明是:元素的 id 现在改为了 thumbnail,请用这个 id 选择器,套用上面的示例 CSS 值,为卡片加上 box-shadow

配套的种子代码(# --seed--## --seed-contents--)是一段完整的 HTML+CSS 文档,模拟了一张"Alphabet/Google 创始人"信息卡片:

<style>
  h4 {
    text-align: center;
    background-color: rgba(45, 45, 45, 0.1);
    padding: 10px;
    font-size: 27px;
  }
  p {
    text-align: justify;
  }
  .links {
    text-align: left;
    color: black;
  }



  .fullCard {
    width: 245px;
    border: 1px solid #ccc;
    border-radius: 5px;
    margin: 10px 5px;
    padding: 4px;
  }
  .cardContent {
    padding: 10px;
  }
  .cardText {
    margin-bottom: 30px;
  }
</style>
<div class="fullCard" id="thumbnail">
  <div class="cardContent">
    <div class="cardText">
      <h4>Alphabet</h4>
      <hr>
      <p><em>Google was founded by Larry Page and Sergey Brin while they were <u>Ph.D. students</u> at <strong>Stanford University</strong>.</em></p>
    </div>
    <div class="cardLinks">
      <a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a><br><br>
      <a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
    </div>
  </div>
</div>

这段种子代码的结构值得注意:

  • 外层 div.fullCard 同时带有 id="thumbnail"——id 选择器 #thumbnail 与 class 选择器 .fullCard 指向同一个元素,卡片已有的 border(1px 实线 #ccc)、border-radius(5px 圆角)、width: 245px 等样式构成了阴影作用的基础外形;
  • 内部的 h4 标题、hr 分隔线、说明段落和两条 Wikipedia 外链,正是此前若干道练习题(text-alignstronguemhr 等)逐步搭建起来的内容,本题在其之上继续叠加视觉效果——这体现了该教学块"在同一张卡片上逐题累加样式"的课程设计思路。

标准答案:用 id 选择器落位阴影

题目 # --solutions-- 小节给出的参考答案,与种子代码的唯一差异就是新增了一个 #thumbnail 规则块:

<style>
  h4 {
    text-align: center;
    background-color: rgba(45, 45, 45, 0.1);
    padding: 10px;
    font-size: 27px;
  }
  p {
    text-align: justify;
  }
  .links {
    text-align: left;
    color: black;
  }
  #thumbnail {
    box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
  }
  .fullCard {
    width: 245px;
    border: 1px solid #ccc;
    border-radius: 5px;
    margin: 10px 5px;
    padding: 4px;
  }
  .cardContent {
    padding: 10px;
  }
  .cardText {
    margin-bottom: 30px;
  }
</style>
<!-- 其余 div 结构与种子代码完全相同 -->

要点:答案把阴影写在了 id 选择器 #thumbnail 下,而不是 class 选择器 .fullCard 下——题目指令明确要求"With this selector"(用这个 id 选择器)。id 选择器优先级高于 class 选择器,后续其他题目若再调整 .fullCard 的样式,也不影响这条阴影声明;这也是在既有样式表上增量开发时的一种稳妥习惯。

测试断言分析:题目如何自动判定你的答案

# --hints-- 小节中的两段测试代码是判定通过与否的真正依据,它们直接对学员提交的全部代码做正则匹配:

第一条:必须存在 #thumbnail { 规则块,且其中出现 box-shadow 属性。

assert.match(code,(/#thumbnail\s*?{\s*?box-shadow/g));

第二条:box-shadow 的取值必须与标准值逐段一致——两层阴影的偏移、模糊半径和 rgba 颜色都要匹配(正则中的 \s*? 允许数字与单位、参数之间存在任意空白,gi 标志使其忽略大小写且全局匹配)。

assert(
  code.match(
    /box-shadow:\s*?0\s+?10px\s+?20px\s+?rgba\(\s*?0\s*?,\s*?0\s*?,\s*?0\s*?,\s*?0?\.19\)\s*?,\s*?0\s+?6px\s+?6px\s+?rgba\(\s*?0\s*?,\s*?0\s*?,\s*?0\s*?,\s*?0?\.23\)/gi
  )
);

从这两条断言可以读出几层设计意图:

  • 第一条用 g(全局)标志,意味着即使样式表里有多条 box-shadow 声明,只要任意一处满足"#thumbnail 块内有 box-shadow"即可命中,但属性必须确实出现在 id 选择器内部——写在 .fullCard 下不会通过;
  • 第二条没有强制"必须只有这一条 box-shadow",但要求取值顺序严格为 0 10px 20px rgba(0,0,0,0.19) 后接 0 6px 6px rgba(0,0,0,0.23),顺序写反(例如把 0.23 那层放前面)会匹配失败——这正是前文强调"参数顺序固定"在教学判定上的直接体现;
  • 正则对空白高度宽容(\s*?/\s+?),学员在 rgba( 0 , 0 , 0 , 0.19 ) 中多敲空格也能通过,降低了判定的脆弱性。

由于该题 challengeType: 0(html),按 challenge-types.tssubmitTypes 的定义,提交动作会走 tests 流程执行上述断言;viewTypes 则决定前端以 classic 经典编辑器视图渲染题目。

题目定义如何变成可运行题目:仓库解析管线

这道题的 Markdown 文件并非直接被网站消费,而是经过 freeCodeCamp 的 curriculum 工具链解析。以本文件为例,解析器入口 tools/challenge-parser/parser 使用若干插件按 # --xxx-- 小节切分内容:

  • 种子代码提取插件 add-seed.js 负责读取 # --seed-- 小节,并在其内部定位 ## --seed-contents--——源码中有一行强制校验:## --seed-contents-- must appear in # --seed-- sections,即种子内容小节必须位于 seed 小节之内,与本文件的组织结构一致。该插件还会把代码块内容整理为 challengeFiles 写进题目数据,并顺带剥离 --fcc-editable-region-- 标记(本卡片的 # --seed-- 小节中,种子 HTML 前的空行即为该标记被移除后的位置);
  • 测试与答案插件 add-tests.jsadd-solution.js 则分别消费 # --hints--# --solutions-- 小节;
  • 最终产出的题目对象由 challenge-schema.js 中的 Joi 模式校验,其中 challengeType 被约束为 0–33 的整数(Joi.number().min(0).max(33)),helpCategory 必须取自 JavaScriptHTML-CSSPython 等枚举之一——本块在区块配置中声明的 helpCategory 恰为 HTML-CSS

因此,本文前面逐段分析的 --description----instructions----hints----seed----solutions-- 五个小节,实际上分别对应题目的讲解文本、操作指令、自动判定断言、初始代码与参考解答——这正是 freeCodeCamp 把"一篇教学文档"直接编译为"一道可交互、可判定的练习题"的完整管线。

小结与延伸

回顾本篇从这道课程题中可以带走的核心内容:

  1. box-shadow 的取值按 offset-xoffset-yblur-radiusspread-radiuscolor 的固定顺序解析,后三项可选,多阴影以逗号分隔叠加;
  2. "近处小而深 + 远处大而淡"的双层 rgba(0,0,0,低透明度) 阴影,是卡片类元素制造悬浮立体感的经典配方:box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
  3. 增量修改既有卡片样式时,优先使用题目指定的 id 选择器(#thumbnail)落位新属性,避免与 class 样式耦合;
  4. 从源码结构看,freeCodeCamp 的每道课程题都是一个带 YAML frontmatter 的五段式 Markdown 文件,经 challenge-parser 插件族解析、Joi 模式校验后进入课程体系,测试正则即判定标准。

若要继续沿着本教学块学习,可参考同区块内紧随其后的题目文件(如透明度、text-transform 等,见 applied-visual-design 目录),以及块级定义 applied-visual-design.json 中完整的 50 道题顺序。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389