SyMind / learning

路漫漫其修远兮,吾将上下而求索。
9 stars 1 forks source link

弹簧动画背后的物理原理 #42

Open SyMind opened 1 year ago

SyMind commented 1 year ago

原文:https://blog.maximeheckel.com/posts/the-physics-behind-spring-animations/

胡克定律

首先,弹簧动画之所以叫这个名字,是因为动画本身遵循弹簧的物理特性,也称之为谐波振荡器(Harmonic Oscillator)。这个术语和与之相关的数学可能看起来非常可怕和复杂,但我对它十分了解,我会尽可能地简化这些内容。当我在大学时,我们这样定义一个谐波振荡器:

一个系统,当偏离其平衡时,会受到与偏移量 x 成正比的力 F。

这种力的公式被称为胡克定律,定义如下:

F = -k*x

其中 k 是一个正数,被称为弹性系数(stiffness),我们可以写成:

力 = 负的 弹性系数 * 偏移量

这意味着:

也许你可能在学校或 Youtube 科技类频道听说过力等于物体的质量乘以它的加速度,它转化为以下公式:

F = m*a

在这里 m 是质量,a 是加速度。

因此,根据这个公式和上面的公式,我们可以推导出:

m*a = -k*x

这相当于

a = -k*x / m

加速度 = 负的 弹性系数 * 偏移量 / 质量

我们现在得到一个方程,可以根据弹簧的偏移量和附着在弹簧上的物体的质量来计算出加速度。而通过加速度我们可以计算出以下状态:

要获得物体的速度,你需要将加速度与先前记录的速度相加,可以转化为以下等式:

v2 = v1 + a*t

速度 = 原速度 + 加速度 * 时间

最后,我们可以计算出位置,因为它遵循类似的规律:物体的位置等于原位置加上速度:

p2 =  p1 + v*t

位置 = 原位置 + 速度 * 时间

相比时间间隔,作为前端开发人员,我们更加熟知的是 帧速率“每秒帧数”。考虑到 Framer Motion 动画的流畅度,我们假设它的弹簧动画以每秒 60 帧的速度运行,因此时间间隔是恒定的,等于 1/600.01666

将数学表达式转换为 JavaScript 代码

现在我们已经完成了数学运算,你可以看到,通过了解物体的质量、弹簧的刚度和位移,我们可以知道在任何给定时间,附着在弹簧上的物体的位置,即任何给定的框架。我们可以在 Javascript 中翻译上面的所有方程,并且对于给定的位移计算对象的所有位置 600 帧,即 10 秒:

const loop = (stiffness, mass) => {
  /* Spring Length, set to 1 for simplicity */
  let springLength = 1;

  /* Object position and velocity. */
  let x = 2;
  let v = 0;

  /* Spring stiffness, in kg / s^2 */
  let k = -stiffness;

  /* Framerate: we want 60 fps hence the framerate here is at 1/60 */
  let frameRate = 1 / 60;

  /* Initiate the array of position and the current framerate i to 0 */
  let positions = [];
  let i = 0;

  /* We loop 600 times, i.e. for 600 frames which is equivalent to 10s*/
  while (i < 600) {
    let Fspring = k * (x - springLength);

    let a = Fspring / mass;
    v += a * frameRate;
    x += v * frameRate;

    i++;

    positions.push({
      position: x,
      frame: i,
    });
  }

  /**
   * positions is an array of number where each number
   * represents the position of the object in a spring
   * motion at a specific frame
   *
   * We use this array to plot all the position of the
   * object for 10 seconds.
   */
  return positions;
};

考虑阻尼(damping)

阻尼是指通过耗散能量使振荡减慢并最终停止的力

它的公式是:

Fd = -d * v

其中 d 是阻尼比,v 是速度

阻尼 = 负的 阻尼比 * 速度

考虑阻尼后会让我们在第一部分建立的加速度公式带来一些变化。我们知道

F = m*a

但这里的 F 是弹力加上阻尼力,而不仅仅是弹力,因此:

Fs + Fd = m*a -> a = (Fs + Fd)/m

我们现在可以将这个新公式添加到我们在上一部分中展示的 Javascript 代码中(我强调了与之前的实现相比我对代码所做的添加):

const loop = (stiffness, mass, damping) => {
  /* Spring Length, set to 1 for simplicity */
  let springLength = 1;

  /* Object position and velocity. */
  let x = 2;
  let v = 0;

  /* Spring stiffness, in kg / s^2 */
  let k = -stiffness;

  /* Damping constant, in kg / s */
  let d = -damping;

  /* Framerate: we want 60 fps hence the framerate here is at 1/60 */
  let frameRate = 1 / 60;

  let positions = [];
  let i = 0;

  /* We loop 600 times, i.e. for 600 frames which is equivalent to 10s*/
  while (i < 600) {
    let Fspring = k * (x - springLength);
    let Fdamping = d * v;

    let a = (Fspring + Fdamping) / mass;
    v += a * frameRate;
    x += v * frameRate;

    i++;

    positions.push({
      position: x,
      frame: i,
    });
  }

  return positions;
};

一个实际例子