首页
/ freeCodeCamp CSS 动画实战:用 cubic-bezier 贝塞尔曲线精确控制 animation-timing-function

freeCodeCamp CSS 动画实战:用 cubic-bezier 贝塞尔曲线精确控制 animation-timing-function

2026-09-06 14:33:25作者:庞队千Virginia

本篇基于 freeCodeCamp 课程 Applied Visual Design 模块中的 "Learn How Bezier Curves Work" 挑战,讲解 CSS 动画中 cubic-bezier() 函数的工作机制:如何在 1×1 坐标系上通过 p1/p2 两个锚点塑造动画的时间曲线,并动手把 linear 关键字替换成等价的贝塞尔曲线写法。读完后你将掌握 animation-timing-function 的底层模型、cubic-bezier(x1, y1, x2, y2) 四个参数的取值含义,并能独立完成一个可运行的双球弹跳动画对比实验。

挑战在课程中的位置

本挑战位于课程结构文件 applied-visual-design.json 所定义的 "Applied Visual Design" 块中,是 CSS 动画系列挑战的关键一环。按照该文件里的 challengeOrder,动画相关挑战的推进顺序是:

  1. "Learn How the CSS @keyframes and animation Properties Work"(keyframes 基础)
  2. "Animate Elements Continually Using an Infinite Animation Count"(无限循环)
  3. "Change Animation Timing with Keywords"(用关键字控制节奏)
  4. "Learn How Bezier Curves Work"(本文主题)
  5. "Use a Bezier Curve to Move a Graphic"(自定义曲线移动图形)
  6. "Make Motion More Natural Using a Bezier Curve"(用曲线模拟自然运动)

其中第 3 步挑战 Change Animation Timing with Keywords 已经引入了 animation-timing-function 属性和 easeease-inease-outlinear 等关键字:ease 起步慢、中段加速、结尾再减速;ease-out 先快后慢;ease-in 先慢后快;linear 全程匀速。本文挑战正是在此基础上引入比关键字更精细的控制手段——贝塞尔曲线。

核心概念:贝塞尔曲线如何驱动动画

在 CSS 动画中,贝塞尔曲线通过 cubic-bezier 函数使用。曲线的形状就代表了动画的播放过程。这条曲线生活在一个 1×1 的坐标系里:

  • X 轴是动画的时长(可以理解为一个时间刻度);
  • Y 轴是动画的变化量(属性的变化进度)。

cubic-bezier 函数由四个主要点位构成,落在 1×1 网格上:p0p1p2p3。其中 p0p3 已经固定好了——它们分别是起点和终点,分别位于原点 (0, 0) 和 (1, 1)。开发者需要自己设置的是另外两个"锚点" p1 和 p2 的 x 与 y 值,把这两个点放在网格中的什么位置,就决定了动画所遵循曲线的形状。在 CSS 中,这通过声明 (x1, y1, x2, y2) 的形式完成,完整示例:

animation-timing-function: cubic-bezier(0.25, 0.25, 0.75, 0.75);

一个值得展开的等价性:linear 的曲线形式

在上面这个示例中,每个点的 x 和 y 值是相等的(x1 = 0.25 = y1,x2 = 0.75 = y2)。按几何学知识,这会产生一条从原点延伸到点 (1, 1) 的直线。因此这个动画在整个动画时长内对元素做线性变化,与直接使用 linear 关键字的效果完全相同——也就是说,它以恒定速度变化。

这一点也可以从仓库中相邻挑战的测试断言得到交叉印证:在关键字挑战的 测试代码 中,linearcubic-bezier(0,0,1,1) 被判定为等价,ease-outcubic-bezier(0,0,0.58,1) 被判定为等价。可见关键字本质上就是几条常见贝塞尔曲线的速记写法,而 cubic-bezier() 则把曲线形状完全交由开发者自定义。

锚点取值范围

从课程后续挑战 Make Motion More Natural Using a Bezier Curve 的讲解可以补充一条重要细节:虽然曲线映射在 1×1 坐标系上、x 值只能取 0 到 1,但 y 值可以设置大于 1 的数值。例如该挑战中 cubic-bezier(0.3, 0.4, 0.5, 1.6) 的 y2 = 1.6 会让动画产生"回弹"式运动,非常适合模拟抛接球的轨迹。理解这一点后,你就能解释为什么 y 轴是"变化量"而非被限制在 0~1 的进度条。

动手练习:完整种子代码

本挑战的原始练习代码(seed)如下。页面上有两个小球 #ball1#ball2,共享同一个 bounce 关键帧动画:从 top: 0pxtop: 249px,时长 2 秒、无限循环。区别在于两者的 animation-timing-function#ball1 使用 linear#ball2 使用 ease-out

<style>

  .balls{
    border-radius: 50%;
    background: linear-gradient(
      35deg,
      #ccffff,
      #ffcccc
    );
    position: fixed;
    width: 50px;
    height: 50px;
    margin-top: 50px;
    animation-name: bounce;
    animation-duration: 2s;
    animation-iteration-count: infinite;
  }
  #ball1 {
    left: 27%;
    animation-timing-function: linear;
  }
  #ball2 {
    left: 56%;
    animation-timing-function: ease-out;
  }

  @keyframes bounce {
    0% {
      top: 0px;
    }
    100% {
      top: 249px;
    }
  }

</style>

<div class="balls" id="ball1"></div>
<div class="balls" id="ball2"></div>

任务要求

对 id 为 ball1 的元素,把 animation-timing-function 属性的值从 linear 改成与其等价的 cubic-bezier 函数值,使用上文示例中给出的点位值,即:

#ball1 {
  left: 27%;
  animation-timing-function: cubic-bezier(0.25, 0.25, 0.75, 0.75);
}

改动后两个小球的运动视觉结果应当完全一致(匀速弹跳),因为 cubic-bezier(0.25, 0.25, 0.75, 0.75)linear 数学等价;#ball2 保持 ease-out 不变,作为"先快后慢"的对照组。

验证方式:测试断言如何检查你的答案

本挑战通过以下两段断言验证结果,也展示了 freeCodeCamp 课程如何检验 CSS 动画属性。

第一段:读取 #ball1 的计算样式,要求其 animationTimingFunction 严格等于 cubic-bezier(0.25, 0.25, 0.75, 0.75)

const ballOne = document.querySelector('#ball1');
const ballOneStyle = window.getComputedStyle(ballOne);
assert.equal(
  ballOneStyle?.animationTimingFunction,
    'cubic-bezier(0.25, 0.25, 0.75, 0.75)'
);

注意这里使用精确相等(assert.equal)而非"二选一"判断——因为本任务的目标就是写出曲线形式,写回 linear 关键字会被判为未达成目标。

第二段:确认 #ball2animation-timing-function 未被改动,它应当保持 ease-out。这里通过 __helpers.removeWhiteSpace 工具去掉空白后再做比较,因此 ease-out 或其计算样式展开形式 cubic-bezier(0,0,0.58,1) 都能通过:

const ballTwoElement = document.querySelector('#ball2');
const ballTwoStyle = window.getComputedStyle(ballTwoElement);
const ball2Animation = __helpers.removeWhiteSpace(ballTwoStyle?.animationTimingFunction);
assert.isTrue(
  ball2Animation == 'ease-out' || ball2Animation == 'cubic-bezier(0,0,0.58,1)'
);

从这两段断言可以推断出浏览器的一个行为细节:getComputedStyle 会把关键字形式的 timing function 展开为 cubic-bezier(...) 形式返回(例如 ease-out 读出来可能是 cubic-bezier(0, 0, 0.58, 1)),所以断言必须同时接受两种写法。这也反过来佐证了"关键字只是贝塞尔曲线的别名"这一核心结论。

参考答案

完整的通过代码(与种子代码的唯一差异在第 20 行附近):

<style>

  .balls{
    border-radius: 50%;
    background: linear-gradient(
      35deg,
      #ccffff,
      #ffcccc
    );
    position: fixed;
    width: 50px;
    height: 50px;
    margin-top: 50px;
    animation-name: bounce;
    animation-duration: 2s;
    animation-iteration-count: infinite;
  }
  #ball1 {
    left: 27%;
    animation-timing-function: cubic-bezier(0.25, 0.25, 0.75, 0.75);
  }
  #ball2 {
    left: 56%;
    animation-timing-function: ease-out;
  }

  @keyframes bounce {
    0% {
      top: 0px;
    }
    100% {
      top: 249px;
    }
  }
</style>
<div class="balls" id="ball1"></div>
<div class="balls" id="ball2"></div>

关键帧如何与曲线配合

种子代码中的 @keyframes bounce 只定义了 0%top: 0px)和 100%top: 249px)两个关键帧。由于 animation-timing-function 作用于相邻两个关键帧之间的插值过程,这里的曲线决定了球从顶端落到底端这一个完整下落段的节奏。如果把 linear 换成 ease-out(如 #ball2),球在 2 秒的前半段就会快速下坠、后半段趋近停稳——两个球同时出发、同时到达终点,但途中位置不同。这正是本挑战希望你用眼睛观察到的对比效果,也是理解曲线形状与"变化量对时间"斜率关系的直接依据。

后续挑战:把曲线真正用起来

本挑战是"建立模型"的一步,课程紧接着安排了两步"应用模型"的挑战,二者与本挑战共用同一套坐标系概念:

  1. Use a Bezier Curve to Move a Graphic:用 cubic-bezier(0, 0, 0.58, 1) 模拟 ease-out。该挑战的讲解详细拆解了锚点如何改变进度——由于 p1 的 y 值为 0、p2 的 y 值为 1,动画在 Y 轴(变化量)上的推进快于 X 轴(时间),即前半段变化快于时间流逝;而曲线末端 y 从 1 保持到 1、x 从 0.58 走到 1,使结尾变化慢于时间流逝,整体呈现"先快后慢"。
  2. Make Motion More Natural Using a Bezier Curve:用 y2 > 1 的曲线(如 cubic-bezier(0.311, 0.441, 0.444, 1.649))模拟抛接球的回弹轨迹,并说明了 animation-timing-function 在每个关键帧处都会重新开始应用这一行为。

配合前一步的关键字挑战 Change Animation Timing with Keywords,三个挑战构成"关键字 → 曲线原理 → 曲线应用"的完整学习路径。

关键要点回顾

  • animation-timing-function 控制属性变化相对于时间的节奏,曲线画在 1×1 坐标系上:X 轴为动画时长,Y 轴为变化量。
  • cubic-bezier(x1, y1, x2, y2) 中 p0 (0,0) 与 p3 (1,1) 固定,开发者只控制 p1、p2 两个锚点。
  • cubic-bezier(0.25, 0.25, 0.75, 0.75) 是对角线直线,与 linear 完全等价,表示匀速变化。
  • 关键字(如 ease-out)本质上是预定义曲线的别名,浏览器计算样式会把它们展开为 cubic-bezier(...) 形式,这也是课程测试断言同时接受两种写法的原因。
  • x 值限定在 0 到 1 之间,y 值可以超过 1 或小于 0,用于制造回弹、超前等自然运动效果。

相关课程文件参考:

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