Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
86 lines
3.3 KiB
Plaintext
86 lines
3.3 KiB
Plaintext
import { warning, secondsToMilliseconds, millisecondsToSeconds } from 'motion-utils';
|
|
import { clamp } from '../../../utils/clamp.mjs';
|
|
import { springDefaults } from './defaults.mjs';
|
|
|
|
const safeMin = 0.001;
|
|
function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
|
|
let envelope;
|
|
let derivative;
|
|
warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less");
|
|
let dampingRatio = 1 - bounce;
|
|
/**
|
|
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
*/
|
|
dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
|
|
duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
|
|
if (dampingRatio < 1) {
|
|
/**
|
|
* Underdamped spring
|
|
*/
|
|
envelope = (undampedFreq) => {
|
|
const exponentialDecay = undampedFreq * dampingRatio;
|
|
const delta = exponentialDecay * duration;
|
|
const a = exponentialDecay - velocity;
|
|
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
const c = Math.exp(-delta);
|
|
return safeMin - (a / b) * c;
|
|
};
|
|
derivative = (undampedFreq) => {
|
|
const exponentialDecay = undampedFreq * dampingRatio;
|
|
const delta = exponentialDecay * duration;
|
|
const d = delta * velocity + velocity;
|
|
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
const f = Math.exp(-delta);
|
|
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
return (factor * ((d - e) * f)) / g;
|
|
};
|
|
}
|
|
else {
|
|
/**
|
|
* Critically-damped spring
|
|
*/
|
|
envelope = (undampedFreq) => {
|
|
const a = Math.exp(-undampedFreq * duration);
|
|
const b = (undampedFreq - velocity) * duration + 1;
|
|
return -safeMin + a * b;
|
|
};
|
|
derivative = (undampedFreq) => {
|
|
const a = Math.exp(-undampedFreq * duration);
|
|
const b = (velocity - undampedFreq) * (duration * duration);
|
|
return a * b;
|
|
};
|
|
}
|
|
const initialGuess = 5 / duration;
|
|
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
duration = secondsToMilliseconds(duration);
|
|
if (isNaN(undampedFreq)) {
|
|
return {
|
|
stiffness: springDefaults.stiffness,
|
|
damping: springDefaults.damping,
|
|
duration,
|
|
};
|
|
}
|
|
else {
|
|
const stiffness = Math.pow(undampedFreq, 2) * mass;
|
|
return {
|
|
stiffness,
|
|
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
duration,
|
|
};
|
|
}
|
|
}
|
|
const rootIterations = 12;
|
|
function approximateRoot(envelope, derivative, initialGuess) {
|
|
let result = initialGuess;
|
|
for (let i = 1; i < rootIterations; i++) {
|
|
result = result - envelope(result) / derivative(result);
|
|
}
|
|
return result;
|
|
}
|
|
function calcAngularFreq(undampedFreq, dampingRatio) {
|
|
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
}
|
|
|
|
export { calcAngularFreq, findSpring };
|