Spring Animation

阻尼振动

阻尼振动的微分方程

其中

  • 称为无阻尼振动的频率
  • 称为阻尼比

这是一个微分方程,求解它需要捡一下高等数学解微分方程的知识。

常系数齐次线性微分方程的通解公式

形如:

的微分方程称为常系数齐次微分方程,它的通解如下:

特征方程的两个根微分方程 的通解
两个不相等的实根
两个相等的实根
两个共轭复根

这里引入两个双曲线函数:

不难看出:

因此当特征方程有两个不相等的实根时,通解可以写成:

因此通解也可以写成:

特征方程 的两个根微分方程 的通解
两个不相等的实根
两个相等的实根
两个共轭复根

在给定初值条件下,第二种写法更容易求解待定系数。

求解阻尼振动

根据常系数齐次微分方程的求解公式,首先计算阻尼振动的特征根:

记衰减系数,频率。套入常系数微分方程的求解公式,得到:

阻尼系数阻尼振动通解
欠阻尼
临界阻尼
过阻尼

阻尼振动特解

在给定初始位移和初始速度的情况下,分别讨论下阻尼振动的特解

欠阻尼

此时

于是有:

临界阻尼

此时

于是有:

过阻尼

此时

于是有:

代码实现

const { sin, cos, sinh, cosh, exp, sqrt, abs } = Math;
 
const spring = (
  x0: number,
  v0: number,
  t: number,
  zeta: number,
  lambda: number,
  omega1: number,
) => {
  const et = exp(-lambda * t);
 
  if (zeta === 1) {
    const a = x0;
    const b = v0 + lambda * a;
 
    return {
      x: (a + b * t) * et,
      v: b * et - lambda * (a + b * t) * et,
    };
  }
 
  const [s, c] = zeta < 1 ? [sin, cos] : [sinh, cosh];
  const omega1_t = omega1 * t;
  const a = x0;
  const b = (v0 + lambda * a) / omega1;
  const x = et * (a * c(omega1_t) + b * s(omega1_t));
 
  return {
    x,
    v:
      -lambda * x +
      omega1 * et * ((zeta < 1 ? -1 : 1) * a * s(omega1_t) + b * c(omega1_t)),
  };
};
const { sin, cos, sinh, cosh, exp, sqrt, abs } = Math;
 
const spring = (
  x0: number,
  v0: number,
  t: number,
  zeta: number,
  lambda: number,
  omega1: number,
) => {
  const et = exp(-lambda * t);
 
  if (zeta === 1) {
    const a = x0;
    const b = v0 + lambda * a;
 
    return {
      x: (a + b * t) * et,
      v: b * et - lambda * (a + b * t) * et,
    };
  }
 
  const [s, c] = zeta < 1 ? [sin, cos] : [sinh, cosh];
  const omega1_t = omega1 * t;
  const a = x0;
  const b = (v0 + lambda * a) / omega1;
  const x = et * (a * c(omega1_t) + b * s(omega1_t));
 
  return {
    x,
    v:
      -lambda * x +
      omega1 * et * ((zeta < 1 ? -1 : 1) * a * s(omega1_t) + b * c(omega1_t)),
  };
};
import './styles.css';

import { useControls } from 'leva';
import { useState } from 'react';

import { useSpring } from './use-spring';

export default function App() {
  const config = useControls({
    stiffness: 100,
    damping: 20,
    mass: 1,
  });

  const [target, setTarget] = useState(0);
  const x = useSpring(target, config);

  return (
    <div className="App">
      <div
        className="box"
        style={{ transform: `translateX(${x}px) rotate(${x}deg)` }}
      ></div>
      <button onClick={() => setTarget((v) => (v === 0 ? 594 : 0))}>
        animate
      </button>
    </div>
  );
}

Advanced Demo

当然少不了Chat Head Demo了。

import './styles.css';

import { useEffect, useState } from 'react';

import { useSpring } from './use-spring';

const useMouseCoords = () => {
  const [coords, setCoords] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const onMove = ({ pageX, pageY }: MouseEvent) => {
      setCoords({ x: pageX, y: pageY });
    };

    window.addEventListener('mousemove', onMove);

    return () => {
      window.removeEventListener('mousemove', onMove);
    };
  }, []);

  return coords;
};

const useCoordsSpring = ({ x, y }: { x: number; y: number }) => {
  return { x: useSpring(x), y: useSpring(y) };
};

const Head = ({ x, y, src }: { x: number; y: number; src: string }) => {
  return (
    <img
      src={src}
      alt=""
      style={{ transform: `translate(-50%, -50%) translate(${x}px, ${y}px)` }}
    ></img>
  );
};

export default function App() {
  const coords0 = useMouseCoords();
  const coords1 = useCoordsSpring(coords0);
  const coords2 = useCoordsSpring(coords1);
  const coords3 = useCoordsSpring(coords2);
  const coords4 = useCoordsSpring(coords3);

  return (
    <div className="App">
      <Head {...coords4} src="https://res.cloudinary.com/dotjkibz4/image/upload/v1691296756/blog/chat-head/2_jzftwn.jpg" />
      <Head {...coords3} src="https://res.cloudinary.com/dotjkibz4/image/upload/v1691296756/blog/chat-head/4_iswuwo.jpg" />
      <Head {...coords2} src="https://res.cloudinary.com/dotjkibz4/image/upload/v1691296756/blog/chat-head/5_uyiw9u.jpg" />
      <Head {...coords1} src="https://res.cloudinary.com/dotjkibz4/image/upload/v1691296756/blog/chat-head/3_ejkp1q.jpg" />
      <Head {...coords0} src="https://res.cloudinary.com/dotjkibz4/image/upload/v1691296754/blog/chat-head/1_p5945e.jpg" />
    </div>
  );
}

参考连接

wobble A tiny (~1.7 KB gzipped) spring physics micro-library that models a damped harmonic oscillator.

use-spring Hooke's law hook.