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.