首页
/ freeCodeCamp 响应式网页设计实战:用 CSS @keyframes 改变按钮的 Hover 状态

freeCodeCamp 响应式网页设计实战:用 CSS @keyframes 改变按钮的 Hover 状态

2026-09-06 14:05:30作者:戚魁泉Nursing

本篇围绕 freeCodeCamp 课程仓库中的挑战文件 use-css-animation-to-change-the-hover-state-of-a-button("Use CSS Animation to Change the Hover State of a Button")展开。你将学会如何用 @keyframes 规则配合 :hover 伪类,让按钮在悬停时平滑地改变背景色,并完整掌握该课程挑战的文件结构(seed 代码、测试断言、参考解答)与在 Responsive Web Design 认证中的位置,以及它如何衔接下一节 animation-fill-mode 的内容。

挑战目标:让按钮悬停时背景色变为 #4791d0

该挑战属于 freeCodeCamp 的 Responsive Web Design(响应式网页设计)认证,位于 applied-visual-design 模块中。从该模块的结构文件看,它在整个挑战顺序中紧随前置课 Learn How the CSS @keyframes and animation Properties Work(id 587d78a7367417b2b2512adf)之后,而模块又作为 responsive-web-design 超级块的第三个 block(在 basic-html-and-html5、basic-css 之后)出现,因此本课的动画知识建立在前面 CSS 基础之上。

挑战的任务描述(instructions)非常明确:

使用 CSS @keyframes 改变 button 元素的 background-color,使它在用户悬停(hover)时变为 #4791d0@keyframes 规则中只需要一个 100% 的条目。

同时文档特别提示:ms 是毫秒(milliseconds)的缩写,1000ms 等于 1s。本挑战中 animation-duration: 500ms 即表示动画持续 0.5 秒。

先理解原理:@keyframes 与动画属性的工作方式

官方给出的演示思路是:可以用 CSS @keyframes 在悬停状态中改变按钮的颜色。文档先举了一个"鼠标悬停时改变图片宽度"的例子:

<style>
  img {
    width: 30px;
  }
  img:hover {
    animation-name: width;
    animation-duration: 500ms;
  }

  @keyframes width {
    100% {
      width: 40px;
    }
  }
</style>

<img src="https://cdn.freecodecamp.org/curriculum/applied-visual-design/google-logo.png" alt="Google's Logo" />

这段代码包含三个关键部分,也是本挑战的核心模式:

  1. :hover 伪类 + animation-name:把动画"绑定"到元素的悬停状态。animation-name 的值(这里是 width)就是后面 @keyframes 规则声明的动画名称,两者靠名字匹配。
  2. animation-duration:设定动画的持续时间,500ms 即 0.5 秒。
  3. @keyframes width:定义动画过程中"发生什么"。这里只写了 100% 一个帧,表示动画结束时的状态是 width: 40px;起始状态(0%)则沿用元素原有的 width: 30px,浏览器会在 500ms 内自动插值过渡。

前序课程 587d78a7367417b2b2512adf 对这一机制有更完整的讲解:animation-name 设定动画名称供 @keyframes 关联,animation-duration 设定动画时长,而 @keyframes 通过 0% 到 100% 的百分比"帧"指定每一阶段元素的样式——可以类比为电影,0% 是开场画面,100% 是片尾画面,CSS 负责在中间"演绎"过渡。

初始代码(seed)与需要补充的部分

挑战文件中的 --seed-contents-- 给出了编辑器的初始代码,它已经完成了 button 的基础样式和悬停动画的声明,唯独缺少 @keyframes 规则:

<style>
  button {
    border-radius: 5px;
    color: white;
    background-color: #0F5897;
    padding: 5px 10px 8px 10px;
  }

  button:hover {
    animation-name: background-color;
    animation-duration: 500ms;
  }


</style>

<button>Register</button>

其中 padding: 5px 10px 8px 10px 是四值简写,分别对应上、右、下、左的填充;按钮初始背景色为 #0F5897button:hover 里已经声明了 animation-name: background-coloranimation-duration: 500ms,但此时 background-color 这个动画名还没有对应的 @keyframes 定义——这正是学习者要补上的空白。

参考解答:补全 @keyframes 规则

文件 --solutions-- 部分给出的完整参考解答如下,在 button:hover 规则之后新增了一条 @keyframes background-color 规则:

<style>
  button {
    border-radius: 5px;
    color: white;
    background-color: #0F5897;
    padding: 5px 10px 8px 10px;
  }

  button:hover {
    animation-name: background-color;
    animation-duration: 500ms;
  }

  @keyframes background-color {
    100% {
      background-color: #4791d0;
    }
  }
</style>
<button>Register</button>

执行逻辑是:当鼠标悬停在按钮上时,button:hover 规则生效,触发名为 background-color 的动画;由于 @keyframes 只定义了 100% 帧(background-color: #4791d0),动画从按钮当前的 #0F5897 出发,在 500ms 内平滑过渡到 #4791d0

测试如何判定通过:两条正则断言

该挑战的验证逻辑写在 --hints-- 部分(hints 中的代码块即实际的 testString)。从源码看,测试采用正则表达式对编辑区代码做模式匹配,共有两条断言:

// 断言 1:必须存在名为 background-color 的 @keyframes 规则
assert.match(code, /@keyframes\s+?background-color\s*?{/g);

// 断言 2:该规则中必须有 100% 帧,且把 background-color 设为 #4791d0
assert.match(code, /100%\s*?{\s*?background-color:\s*?#4791d0;\s*?}/gi);

第一条正则要求代码中出现 @keyframes background-color {(空白字符允许弹性匹配);第二条要求在 100% 帧内写出 background-color: #4791d0;,且末尾分号与花括号结构完整。这解释了为什么任务强调"只写一个 100% 条目"——多写 0% 帧不会破坏断言,但会偏离课程意图(起始状态应由元素原有样式承担)。

从课程架构角度看,这种"挑战 = Markdown 分节 + 正则/JS 断言"的组织方式是统一的:challenge-schema.js 中的 Joi 模式要求每个挑战必须具备 tests 数组(含 texttestString)以及 solutions 等字段,seed、hints、solutions 都是被该模式校验后入库的结构化内容。

常见陷阱:动画结束后颜色回弹

这个写法有一个值得注意的行为:动画只定义了 500ms 的过程,鼠标保持悬停时动画播放完毕后,按钮会回落到原始背景色 #0F5897,而不是停留在 #4791d0。这是本挑战刻意保留的"未完成态"——它由下一课专门解决。

紧随其后的挑战 Modify Fill Mode of an Animation 指出:"Notice how the animation resets after 500ms has passed, causing the button to revert back to the original color. You want the button to stay highlighted."(注意动画在 500ms 后复位,按钮回到原色。你希望按钮保持高亮。)其解法是在 button:hover 中追加:

animation-fill-mode: forwards;

animation-fill-mode 控制动画结束后应用到元素上的样式;取值为 forwards 时,元素会保持 100% 帧的样式(即停留在 #4791d0),直到鼠标移开。至此,"悬停变色"的交互才算完整。

小结:这条学习链路的脉络

环节 内容 关键文件
前置原理 animation-name / animation-duration / @keyframes 百分比帧的工作方式 587d78a7367417b2b2512adf.md
本课任务 :hover 状态下用 @keyframes 把按钮背景色动画到 #4791d0@keyframes 只含 100% 帧 587d78a7367417b2b2512ae0.md
后续衔接 animation-fill-mode: forwards 让按钮悬停时保持高亮色 58a7a6ebf9a6318348e2d5aa.md
模块/认证位置 applied-visual-design 是 responsive-web-design 认证的第 3 个 block applied-visual-design.jsonresponsive-web-design.json

掌握本课的关键收获可以概括为三点:一是 :hover 伪类可以承载 animation-* 属性,把动画触发与交互状态绑定;二是 @keyframes 只写终点帧(100%)时,起点自动取元素当前值,适合做"从 A 变到 B"的两端动画;三是 animation-duration 支持 ms/s 两种时间单位(1000ms = 1s)。按挑战文件中的 seed 与 solutions 对照练习后,再进入 fill-mode 一课,即可完整实现一个"悬停后保持高亮"的按钮动画效果。

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