首页
/ freeCodeCamp 响应式 Web 设计实战:用 animation-fill-mode 让 CSS 动画结束后保留最终状态

freeCodeCamp 响应式 Web 设计实战:用 animation-fill-mode 让 CSS 动画结束后保留最终状态

2026-09-06 14:42:20作者:温玫谨Lighthearted

本篇基于 freeCodeCamp 课程中的挑战文档《Modify Fill Mode of an Animation》(curriculum/challenges/english/blocks/applied-visual-design/58a7a6ebf9a6318348e2d5aa.md)展开,讲解 animation-fill-mode: forwards 的作用机制:为什么 CSS 动画在结束后会“弹回”初始样式,以及如何用一行声明让按钮在悬停期间保持高亮色。读完本文,你将理解 CSS 动画的“填充模式”概念,掌握在 :hover 动画场景中让动画终值持久的完整写法,并了解该挑战在课程仓库中的组织方式与测试校验逻辑。

1. 问题起点:动画结束后按钮颜色“弹回”

在 freeCodeCamp 的 Responsive Web Design(响应式 Web 设计)认证中,applied-visual-design(Applied Visual Design)模块 的前两道相关挑战已经完成了基础铺垫:

  1. Learn How the CSS @keyframes and animation Properties Work:介绍 animation-nameanimation-duration@keyframes 的基本用法;
  2. Use CSS Animation to Change the Hover State of a Button:用 @keyframes 让按钮的 background-color 在悬停时变为 #4791d0

本文对应的挑战文档接着指出上一阶段代码存在的缺陷:

That's great, but it doesn't work right yet. 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 规则里的原始背景色 #0F5897),导致按钮在悬停时出现“高亮一下又变回去”的闪烁观感,与“悬停期间持续高亮”的设计意图相悖。

挑战给出的初始种子代码(seed)如下,修改区域被注释明确标出:

<style>
  button {
    border-radius: 5px;
    color: white;
    background-color: #0F5897;
    padding: 5px 10px 8px 10px;
  }
  button:hover {
    animation-name: background-color;
    animation-duration: 500ms;
    /* Only change code below this line */

    /* Only change code above this line */
  }
  @keyframes background-color {
    100% {
      background-color: #4791d0;
    }
  }
</style>
<button>Register</button>

这段代码里几个关键点值得注意:

  • @keyframes background-color 只定义了 100% 一帧(终帧为 #4791d0),没有定义 0% 帧。从源码结构看,这是刻意的设计:起始状态由元素自身的 background-color: #0F5897 提供,CSS 引擎在动画期间负责从当前值插值到终帧值。
  • animation-name: background-colorbutton:hover 规则与同名 @keyframes 规则关联起来——这一命名绑定关系在前一挑战(587d78a7367417b2b2512adf.md)中已做过系统讲解。
  • animation-duration: 500ms 规定动画持续 500 毫秒,其中 ms 为毫秒单位,1000ms 等于 1s。

2. 核心解法:animation-fill-mode: forwards

挑战文档对问题的诊断结论是:

This can be done by setting the animation-fill-mode property to forwards. The animation-fill-mode specifies the style applied to an element when the animation has finished.

animation-fill-mode 用于指定动画结束之后(以及可选地,动画开始之前)应用哪些样式。取值为 forwards 时,动画结束后元素会继续应用 @keyframes 规则中最后一帧(本例中为 100%)所定义的样式,而不是回退到原始样式。

在种子的修改区域内加入一行声明即可:

animation-fill-mode: forwards;

完整的解题代码(挑战文档 --solutions-- 部分给出的标准答案)为:

<style>
  button {
    border-radius: 5px;
    color: white;
    background-color: #0F5897;
    padding: 5px 10px 8px 10px;
  }
  button:hover {
    animation-name: background-color;
    animation-duration: 500ms;
    animation-fill-mode: forwards;
  }
  @keyframes background-color {
    100% {
      background-color: #4791d0;
    }
  }
</style>
<button>Register</button>

此时的行为是:鼠标悬停到按钮上,动画播放 500ms 将背景色从 #0F5897 过渡到 #4791d0;500ms 后动画结束,由于 fill-mode: forwards,按钮保持在 #4791d0 的高亮状态,直到鼠标移出、:hover 规则失效,元素自然回到基础样式。这正是“按钮悬停高亮”这一常见交互所需要的语义:动画只负责平滑过渡,填充模式负责“守住”终值。

3. 深入理解:fill-mode 的工作机制与常用取值

结合 CSS 动画模型可以进一步拆解这条声明为什么有效。CSS 动画的生命周期中,动画“进行中”与“已停止”是两个阶段。动画停止后,元素的样式默认遵循动画结束时的样式计算规则,此时 animation-fill-mode 决定关键帧样式是否继续“渗透”到元素上。常用取值及其含义如下(本挑战只用到了其一,其余取值在后续课程挑战如“Create Visual Direction by Fading an Element from Left to Right”中会再次出现):

取值 行为
none(默认) 动画结束/延迟期间不应用任何关键帧样式,元素完全回到普通样式。本例“弹回原色”的根源
forwards 动画结束后,持续应用最后一帧的样式直到动画元素被移除或不再匹配
backwards 在动画开始前(延迟阶段)应用第一帧的样式,常用于 animation-delay 场景
both 同时具备 forwardsbackwards 的效果

本挑战中“只定义 100% 帧 + forwards”的组合尤其值得品味:如果 fill-mode 为默认的 none,那么动画结束后连 100% 帧的 #4791d0 都不会保留;换成 forwards 后,这个唯一被显式定义的终帧就成了“悬停期间按钮的稳态”。

4. 挑战如何被自动校验

该挑战的 challengeType: 0,从仓库源码看对应 challenge-types.ts 中的 html = 0,属于“经典 HTML 挑战”,viewTypes 映射为 classic 视图、submitTypestests——即通过运行测试代码来判定是否通过。

挑战文档的 --hints-- 部分内嵌了三条正则断言,它们就是判题依据:

assert.match(code,/button\s*?:\s*?hover\s*?{[\s\S]*animation-fill-mode\s*?:\s*?forwards\s*?;[\s\S]*}/gi);
assert.match(code,/button\s*?:\s*?hover\s*?{[\s\S]*animation-name\s*?:\s*?background-color\s*?;[\s\S]*}/gi);
assert.match(code,/button\s*?:\s*?hover\s*?{[\s\S]*animation-duration\s*?:\s*?500ms\s*?;[\s\S]*}/gi );

三条断言分别校验:

  1. button:hover 规则块内存在 animation-fill-mode: forwards;(本挑战的新增要求);
  2. button:hover 规则块内仍然存在 animation-name: background-color;(继承自前一挑战,不能删改);
  3. button:hover 规则块内仍然存在 animation-duration: 500ms;(同上)。

这种“增量式校验”体现了课程的设计思路:每一道挑战只新增一条 CSS 声明,但测试会同时守护已有声明不被破坏,从而保证学习者沿着一小步、一小步地构建出完整正确的样式。断言使用的正则对空白字符做了宽松匹配(\s*?),因此属性的书写顺序、换行与缩进都不影响判题,唯一硬性要求是三条声明都位于 button:hover 的选择器块之内。

5. 在课程仓库中的位置与上下文

该挑战文档的 frontmatter 记录了它的元数据:

id: 58a7a6ebf9a6318348e2d5aa
title: Modify Fill Mode of an Animation
challengeType: 0
forumTopicId: 301064
dashedName: modify-fill-mode-of-an-animation

applied-visual-design.jsonchallengeOrder 数组可以确认,58a7a6ebf9a6318348e2d5aa 位于 587d78a7367417b2b2512ae0(Use CSS Animation to Change the Hover State of a Button)之后、587d78a7367417b2b2512ae1(Create Movement Using CSS Animation)之前,处于 CSS 动画小节的过渡位置:先掌握悬停变色动画,再补齐 fill-mode 让它行为正确,随后进入位移、淡入淡出、无限循环等更丰富的动画场景。

再往上一级看,responsive-web-design.json 显示 applied-visual-design 是 Responsive Web Design 认证下第三个 block,排在 basic-html-and-html5basic-css 之后。挑战文档内的 videoUrl 字段则指向配套讲解视频(scrimba 课程),供学习者在动手前先观看概念演示。

6. 小结

  • 症状:button:hover 上的颜色动画播放 500ms 后结束,按钮背景色立即回落到 #0F5897,出现“高亮一下又弹回”的闪烁。
  • 根因:animation-fill-mode 默认取值为 none,动画停止后不保留任何关键帧样式。
  • 解法:在 button:hover 中追加 animation-fill-mode: forwards;,使动画结束后持续应用 @keyframes background-color100% 帧定义的 #4791d0,悬停期间按钮保持高亮。
  • 工程佐证:判题断言(见挑战文档 --hints--)同时校验 animation-fill-modeanimation-nameanimation-duration 三条声明,确保增量修改不破坏既有动画配置。

这条“一行 CSS 解决动画回弹问题”的实践,是理解 CSS 动画生命周期(延迟期 → 播放期 → 结束后填充期)的一个最小完整案例,也是本 block 后续 animation-iteration-countanimation-timing-function 等挑战的重要前置基础。

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